diff --git a/CMakeLists.txt b/CMakeLists.txt
index 16d365355ceb..d229cb0847d0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -234,6 +234,7 @@ include_directories("include")
include_directories("mshadow")
include_directories("3rdparty/cub")
include_directories("nnvm/include")
+include_directories("nnvm/tvm/include")
include_directories("dmlc-core/include")
include_directories("dlpack/include")
@@ -696,4 +697,3 @@ endif()
set(LINT_DIRS "include src plugin cpp-package tests")
set(EXCLUDE_PATH "src/operator/contrib/ctc_include")
add_custom_target(mxnet_lint COMMAND ${CMAKE_COMMAND} -DMSVC=${MSVC} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DLINT_DIRS=${LINT_DIRS} -DPROJECT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DPROJECT_NAME=mxnet -DEXCLUDE_PATH=${EXCLUDE_PATH} -P ${CMAKE_CURRENT_SOURCE_DIR}/dmlc-core/cmake/lint.cmake)
-
diff --git a/Jenkinsfile b/Jenkinsfile
index c23bbbfe5a50..78af1cf021d4 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -38,12 +38,12 @@ def init_git() {
deleteDir()
retry(5) {
try {
- // Make sure wait long enough for api.github.com request quota. Important: Don't increase the amount of
+ // Make sure wait long enough for api.github.com request quota. Important: Don't increase the amount of
// retries as this will increase the amount of requests and worsen the throttling
timeout(time: 15, unit: 'MINUTES') {
checkout scm
- sh 'git submodule update --init'
- sh 'git clean -d -f'
+ sh 'git submodule update --init --recursive'
+ sh 'git clean -d -f'
}
} catch (exc) {
deleteDir()
@@ -61,8 +61,8 @@ def init_git_win() {
// retries as this will increase the amount of requests and worsen the throttling
timeout(time: 15, unit: 'MINUTES') {
checkout scm
- bat 'git submodule update --init'
- bat 'git clean -d -f'
+ bat 'git submodule update --init --recursive'
+ bat 'git clean -d -f'
}
} catch (exc) {
deleteDir()
@@ -332,6 +332,7 @@ try {
make('build_cuda', flag)
pack_lib('gpu')
stash includes: 'build/cpp-package/example/test_score', name: 'cpp_test_score'
+ stash includes: 'build/cpp-package/example/test_optimizer', name: 'cpp_test_optimizer'
}
}
},
@@ -676,6 +677,7 @@ try {
init_git()
unpack_lib('gpu')
unstash 'cpp_test_score'
+ unstash 'cpp_test_optimizer'
timeout(time: max_time, unit: 'MINUTES') {
sh "${docker_run} gpu --dockerbinary nvidia-docker cpp-package/tests/ci_test.sh"
}
diff --git a/Makefile b/Makefile
index cb3e63ba13b0..5d81c7fbb160 100644
--- a/Makefile
+++ b/Makefile
@@ -91,7 +91,7 @@ ifeq ($(DEBUG), 1)
else
CFLAGS += -O3 -DNDEBUG=1
endif
-CFLAGS += -I$(ROOTDIR)/mshadow/ -I$(ROOTDIR)/dmlc-core/include -fPIC -I$(NNVM_PATH)/include -I$(DLPACK_PATH)/include -Iinclude $(MSHADOW_CFLAGS)
+CFLAGS += -I$(ROOTDIR)/mshadow/ -I$(ROOTDIR)/dmlc-core/include -fPIC -I$(NNVM_PATH)/include -I$(DLPACK_PATH)/include -I$(NNVM_PATH)/tvm/include -Iinclude $(MSHADOW_CFLAGS)
LDFLAGS = -pthread $(MSHADOW_LDFLAGS) $(DMLC_LDFLAGS)
ifeq ($(DEBUG), 1)
NVCCFLAGS += -std=c++11 -Xcompiler -D_FORCE_INLINES -g -G -O0 -ccbin $(CXX) $(MSHADOW_NVCCFLAGS)
@@ -356,7 +356,7 @@ ifeq ($(USE_CUDA), 1)
LDFLAGS += -lcuda -lnvrtc
CFLAGS += -DMXNET_ENABLE_CUDA_RTC=1
endif
- # Make sure to add stubs as fallback in order to be able to build
+ # Make sure to add stubs as fallback in order to be able to build
# without full CUDA install (especially if run without nvidia-docker)
LDFLAGS += -L/usr/local/cuda/lib64/stubs
SCALA_PKG_PROFILE := $(SCALA_PKG_PROFILE)-gpu
diff --git a/cpp-package/example/test_optimizer.cpp b/cpp-package/example/test_optimizer.cpp
new file mode 100644
index 000000000000..bf465b786988
--- /dev/null
+++ b/cpp-package/example/test_optimizer.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+#include "mxnet-cpp/MxNetCpp.h"
+
+using namespace std;
+using namespace mxnet::cpp;
+
+int main(int argc, char** argv) {
+ // Confirm >1 optimizers can be created w/o error
+ Optimizer* opt = OptimizerRegistry::Find("sgd");
+ opt = OptimizerRegistry::Find("adam");
+ int ret = (opt == 0) ? 1 : 0;
+
+ MXNotifyShutdown();
+ return ret;
+}
diff --git a/cpp-package/include/mxnet-cpp/optimizer.hpp b/cpp-package/include/mxnet-cpp/optimizer.hpp
index e3d47d1161c6..26fd00f3a162 100644
--- a/cpp-package/include/mxnet-cpp/optimizer.hpp
+++ b/cpp-package/include/mxnet-cpp/optimizer.hpp
@@ -125,13 +125,16 @@ inline float Optimizer::GetWD_(int index) {
}
inline Optimizer* OptimizerRegistry::Find(const std::string& name) {
- MXNETCPP_REGISTER_OPTIMIZER(sgd, SGDOptimizer);
- MXNETCPP_REGISTER_OPTIMIZER(ccsgd, SGDOptimizer); // For backward compatibility
- MXNETCPP_REGISTER_OPTIMIZER(rmsprop, RMSPropOptimizer);
- MXNETCPP_REGISTER_OPTIMIZER(adam, AdamOptimizer);
- MXNETCPP_REGISTER_OPTIMIZER(adagrad, AdaGradOptimizer);
- MXNETCPP_REGISTER_OPTIMIZER(adadelta, AdaDeltaOptimizer);
- MXNETCPP_REGISTER_OPTIMIZER(signum, SignumOptimizer);
+ if (cmap().empty()) {
+ // Optimizers should only be registered once
+ MXNETCPP_REGISTER_OPTIMIZER(sgd, SGDOptimizer);
+ MXNETCPP_REGISTER_OPTIMIZER(ccsgd, SGDOptimizer); // For backward compatibility
+ MXNETCPP_REGISTER_OPTIMIZER(rmsprop, RMSPropOptimizer);
+ MXNETCPP_REGISTER_OPTIMIZER(adam, AdamOptimizer);
+ MXNETCPP_REGISTER_OPTIMIZER(adagrad, AdaGradOptimizer);
+ MXNETCPP_REGISTER_OPTIMIZER(adadelta, AdaDeltaOptimizer);
+ MXNETCPP_REGISTER_OPTIMIZER(signum, SignumOptimizer);
+ }
auto it = cmap().find(name);
if (it == cmap().end())
return nullptr;
diff --git a/cpp-package/tests/ci_test.sh b/cpp-package/tests/ci_test.sh
index 3b2af35bf1be..2042529ace01 100755
--- a/cpp-package/tests/ci_test.sh
+++ b/cpp-package/tests/ci_test.sh
@@ -22,6 +22,9 @@ export LD_LIBRARY_PATH=$(readlink -f ../../lib):$LD_LIBRARY_PATH
echo $LD_LIBRARY_PATH
ls -l ../../lib/
+cp ../../build/cpp-package/example/test_optimizer .
+./test_optimizer
+
cp ../../build/cpp-package/example/test_score .
./get_mnist.sh
./test_score 0.93
diff --git a/dlpack b/dlpack
index a6e09b58dc00..10892ac964f1 160000
--- a/dlpack
+++ b/dlpack
@@ -1 +1 @@
-Subproject commit a6e09b58dc00ee0065f5b7879800e646fbb01d1e
+Subproject commit 10892ac964f1af7c81aae145cd3fab78bbccd297
diff --git a/docs/_static/mxnet-theme/index.html b/docs/_static/mxnet-theme/index.html
index d22e2541903c..3b48832a03cd 100644
--- a/docs/_static/mxnet-theme/index.html
+++ b/docs/_static/mxnet-theme/index.html
@@ -21,9 +21,9 @@
-
Apache MXNet 1.0 Released
-
We're excited to announce the release of MXNet 1.0! Check out the release notes for latest updates.
-
Learn More
+
Apache MXNet 1.1.0 Released
+
We're excited to announce the release of MXNet 1.1.0! Check out the release notes for latest updates.
+
Learn More
MXNet Model Server
diff --git a/docs/build_version_doc/AddVersion.py b/docs/build_version_doc/AddVersion.py
index 2c9ee22bf42e..c4d088a4b0f4 100755
--- a/docs/build_version_doc/AddVersion.py
+++ b/docs/build_version_doc/AddVersion.py
@@ -57,6 +57,9 @@
for name in files:
if not name.endswith('.html'):
continue
+ if 'install' in path:
+ print("Skipping this path: {}".format(path))
+ continue
with open(os.path.join(path, name), 'r') as html_file:
content = bs(html_file, 'html.parser')
navbar = content.find(id="main-nav")
@@ -74,7 +77,7 @@
outstr = str(content).replace('<', '<').replace('>', '>')
# Fix link
if args.current_version == tag_list[0]:
- print("Fixing" + os.path.join(path, name))
+ print("Fixing " + os.path.join(path, name))
outstr = outstr.replace('https://mxnet.io', 'https://mxnet.incubator.apache.org')
outstr = outstr.replace('http://mxnet.io', 'https://mxnet.incubator.apache.org')
else:
diff --git a/docs/build_version_doc/Dockerfile b/docs/build_version_doc/Dockerfile
new file mode 100755
index 000000000000..204320e32611
--- /dev/null
+++ b/docs/build_version_doc/Dockerfile
@@ -0,0 +1,44 @@
+FROM ubuntu:16.04
+LABEL maintainer="markhama@amazon.com"
+
+# Install dependencies
+RUN apt-get update && apt-get install -y \
+ apt-transport-https \
+ build-essential \
+ ca-certificates \
+ curl \
+ doxygen \
+ git \
+ libatlas-base-dev \
+ liblapack-dev \
+ libopenblas-dev \
+ libopencv-dev \
+ pandoc \
+ python-numpy \
+ python-pip \
+ software-properties-common \
+ unzip \
+ wget
+
+# Setup Scala
+RUN echo "deb https://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
+RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
+RUN apt-get update && apt-get install -y \
+ sbt \
+ scala
+
+RUN pip install --upgrade pip && pip install \
+ beautifulsoup4 \
+ breathe \
+ CommonMark==0.5.4 \
+ h5py \
+ mock==1.0.1 \
+ pypandoc \
+ recommonmark==0.4.0 \
+ sphinx==1.5.6
+
+
+COPY *.sh /
+COPY *.py /
+RUN /build_all_version.sh "1.1.0 1.0.0 0.12.1 0.12.0 0.11.0 master"
+RUN /update_all_version.sh "1.1.0 1.0.0 0.12.1 0.12.0 0.11.0 master" 1.1.0 http://mxnet.incubator.apache.org/
diff --git a/docs/build_version_doc/build_all_version.sh b/docs/build_version_doc/build_all_version.sh
index 6a37815fd50e..4db9326a4464 100755
--- a/docs/build_version_doc/build_all_version.sh
+++ b/docs/build_version_doc/build_all_version.sh
@@ -19,67 +19,65 @@
# This script is for locally building website for all versions
# Built files are stored in $built
-# Version numbers are stored in $tag_list.
-# Version numbers are ordered from latest to old and final one is master.
+
+# Takes one argument:
+# * tag list - space delimited list of Github tags; Example: "1.1.0 1.0.0 master"
+# Example Usage:
+# ./build_all_version.sh "1.1.0 1.0.0 master"
+
set -e
set -x
-tag_list="1.1.0 1.0.0 0.12.1 0.12.0 0.11.0 master"
+if [ -z "$1" ]
+ then
+ echo "Please provide a list of version tags you wish to run."
+ exit 1
+ else
+ tag_list="$1"
+ echo "Using these tags: $1"
+fi
mxnet_url="https://github.com/apache/incubator-mxnet.git"
mxnet_folder="apache_mxnet"
built="VersionedWeb"
-mkdir $built
-mkdir "$built/versions"
-git clone $mxnet_url $mxnet_folder --recursive
-cd "$mxnet_folder/docs"
-tag_file="tag_list.txt"
+if [ ! -d "$mxnet_folder" ]; then
+ mkdir $mxnet_folder
+ git clone $mxnet_url $mxnet_folder --recursive
+fi
-# Write all version numbers into $tag_file
-for tag in $tag_list; do
- if [ $tag != 'master' ]
- then
- echo "$tag" >> "$tag_file"
- fi
-done
+if [ ! -d "$built" ]; then
+ mkdir $built
+ mkdir "$built/versions"
+fi
# Build all versions and use latest version(First version number in $tag_list) as landing page.
-version_num=0
for tag in $tag_list; do
+ cd "$mxnet_folder"
+ git fetch
if [ $tag == 'master' ]
- then
- git checkout master
- else
- git checkout "tags/$tag"
+ then
+ git checkout master
+ git pull
+ else
+ git checkout "tags/$tag"
+ fi
+ if [ $tag == '0.11.0' ]
+ then
+ git checkout master -- docs/mxdoc.py
fi
-
git submodule update || exit 1
- cd ..
make clean
cd docs
make clean
- make html USE_OPENMP=0 || exit 1
- python build_version_doc/AddVersion.py --file_path "_build/html/" --current_version "$tag" || exit 1
-
- if [ $tag != 'master' ]
- then
- python build_version_doc/AddPackageLink.py --file_path "_build/html/get_started/install.html" \
- --current_version "$tag" || exit 1
- fi
-
- if [ $version_num == 0 ]
- then
- cp -a _build/html/. "../../$built"
- else
- file_loc="../../$built/versions/$tag"
- mkdir "$file_loc"
- cp -a _build/html/. "$file_loc"
+ make html USE_OPENMP=1 || exit 1
+ cd ../../
+ file_loc="$built/versions/$tag"
+ if [ -d "$file_loc" ] ; then
+ rm -rf "$file_loc"
fi
-
- ((++version_num))
+ mkdir "$file_loc"
+ cp -a "$mxnet_folder/docs/_build/html/." "$file_loc"
done
-
-mv "$tag_file" "../../$built/tag.txt"
-cd ../..
-rm -rf "$mxnet_folder"
+
+echo "Now you may want to run update_all_version.sh to create the production layout with the versions dropdown and other per-version corrections."
diff --git a/docs/build_version_doc/build_doc.sh b/docs/build_version_doc/build_doc.sh
index eefc81e362e8..427f40c592a0 100755
--- a/docs/build_version_doc/build_doc.sh
+++ b/docs/build_version_doc/build_doc.sh
@@ -19,10 +19,22 @@
set -e
set -x
+# This script is run on a nightly basis. Refer to Job: http://jenkins.mxnet-ci.amazon-ml.com/job/incubator-mxnet-build-site/
+# Job should pass in paramters:
+# web_url=https://github.com/apache/incubator-mxnet-site
+# web_branch=asf-site
+# release_branch=v1.1.0 (example). This needs to come from the job config
+
+# First parameter sent by job configuration: https://github.com/apache/incubator-mxnet-site
web_url="$1"
+
+# Second parameter sent by job configuration: asf-site
+web_branch="$2"
+
web_folder="VersionedWeb"
+
local_build="latest"
-web_branch="$2"
+
git clone $web_url $web_folder
cd $web_folder
git checkout $web_branch
@@ -37,28 +49,64 @@ while read -r line
do
tag_list+=("$line")
done < "$tag_list_file"
+
+# This is the first tag found in tag.txt
latest_tag=${tag_list[0]}
-echo "latest_tag is: $latest_tag"
+echo "++++ LATEST TAG found in tag.txt file is : $latest_tag ++++"
+
commit_id=$(git rev-parse HEAD)
+
+# Find the current TAG in GIT
curr_tag=${TAG}
curr_tag=${curr_tag:5}
-echo "Current tag is $curr_tag"
+
+echo "++++ CURRENT TAG IN GIT is $curr_tag ++++"
+
+# If current tag in git is newer than latest tag found in tag.txt
if [[ "$curr_tag" != 'master' ]] && [ $curr_tag != $latest_tag ]
then
+ echo "++++ Found a git TAG $curr_tag newer than mxnet repo tag $latest_tag , we need to build a new release ++++"
+ echo "assigning curr_tag to latest_tag"
latest_tag=$curr_tag
fi
# Build new released tag
if [ $latest_tag != ${tag_list[0]} ]
then
- echo "Building new tag"
+ echo " ****************************************** "
+ echo " Building new release on: $latest_tag "
+ echo " ****************************************** "
git submodule update
+
+ # checkout the latest release tag.
+ echo "++++ Checking out and building new tag $latest_tag ++++"
+ git checkout tags/$latest_tag
make docs || exit 1
- echo -e "$latest_tag\n$(cat $tag_list_file)" > "$tag_list_file"
- cat $tag_list_file
+
tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "docs/_build/html/" --current_version "$latest_tag"
tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddPackageLink.py \
--file_path "docs/_build/html/install/index.html" --current_version "$latest_tag"
+
+ # Update the tag_list (tag.txt).
+ ###### content of tag.txt########
+ #
+ # 1.0.0
+ # 0.12.1
+ # 0.12.0
+ # 0.11.0
+ echo "++++ Adding $latest_tag to the top of the $tag_list_file ++++"
+ echo -e "$latest_tag\n$(cat $tag_list_file)" > "$tag_list_file"
+ cat $tag_list_file
+
+ # The following block does the following:
+ # a. copies the static html that was built from new tag to a local sandbox folder.
+ # b. copies the $tag_list_file into local sandbox tag.txt
+ # c. removes .git in VersionedWeb folder
+ # d. copies VersionedWeb/versions to local sandbox versions folder.
+ # e. makes a new directory with the previous TAG version. N-1 version name (example current: 1.1.0, Previous: 1.0.0)
+ # f. Copies ReadMe.md to the local sandbox build.
+ # g. removes the content of VersionedWeb completely.
+ # f. Adds new content from local sandbox build to VersionedWeb.
cp -a "docs/_build/html/." "$local_build"
cp $tag_list_file "$local_build/tag.txt"
rm -rf "$web_folder/.git"
@@ -69,26 +117,32 @@ then
rm -rf "$local_build/versions/${tag_list[0]}/versions"
rm -rf "$web_folder/*"
cp -a "$local_build/." "$web_folder"
-fi
+
+ echo " ****************************************** "
+ echo " Successfully built new release $latest_tag "
+ echo " ****************************************** "
+else
+ # Build latest master
+ echo " ********** Building Master ************ "
-# Build latest master
-git checkout master
-git checkout -- .
-git submodule update
-echo "Building master"
-make docs || exit 1
+ make docs || exit 1
-rm -rfv $web_folder/versions/master/*
-cp -a "docs/_build/html/." "$web_folder/versions/master"
-tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/master"
+ rm -rfv $web_folder/versions/master/*
+ cp -a "docs/_build/html/." "$web_folder/versions/master"
+ tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/master"
+fi
# Update version list for all previous version website
if [ $latest_tag != ${tag_list[0]} ]
then
total=${#tag_list[*]}
- for (( i=0; i<=$(( $total -1 )); i++ ))
+ for (( i=0; i<=$(( $total - 1 )); i++ ))
+
do
tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/${tag_list[$i]}" \
--current_version "${tag_list[$i]}"
done
+
+ # Update master version dropdown
+ tests/ci_build/ci_build.sh doc python docs/build_version_doc/AddVersion.py --file_path "$web_folder/versions/master"
fi
diff --git a/docs/build_version_doc/build_site_tag.sh b/docs/build_version_doc/build_site_tag.sh
new file mode 100755
index 000000000000..d453e0cc9734
--- /dev/null
+++ b/docs/build_version_doc/build_site_tag.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+# 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.
+
+
+# How this script works:
+# 1. Receive tag list
+# Looks like: tag_list="1.1.0 1.0.0 0.12.1 0.12.0 0.11.0 master"
+# 2. Receive default tag (for main website view)
+# 3. Receive root URL
+# 4. Call build and then update scripts
+
+# Take user input or check env var for tag list
+if [ -z "$1" ]
+ then
+ echo "No tag list supplied... trying environment variable $TAG_LIST"
+ else
+ tag_list="${TAG_LIST:-"$1"}"
+ echo "Using these tags: $1"
+fi
+
+if [ -z "$tag_list" ]
+ then
+ echo "No tags defined"
+ exit 1
+fi
+
+if [ -z "$2" ]
+ then
+ echo "Please pick a version to use as a default for the website. Ex: 1.1.0"
+ exit 1
+ else
+ tag_default=$2
+fi
+
+if [ -z "$3" ]
+ then
+ echo "Please provide the root url for the site. Ex: http://mxnet.incubator.apache.org/"
+ exit 1
+ else
+ root_url=$3
+fi
+
+# Pass params to build and update scripts
+for tag in $tag_list; do
+ ./build_all_version.sh $tag || exit 1
+done
+
+./update_all_version.sh "$tag_list" $tag_default $root_url || exit 1
+
diff --git a/docs/build_version_doc/setup_docs_ubuntu.sh b/docs/build_version_doc/setup_docs_ubuntu.sh
new file mode 100755
index 000000000000..98b9259c6966
--- /dev/null
+++ b/docs/build_version_doc/setup_docs_ubuntu.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+# 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.
+
+
+# If you need to build <= v0.12.0 then use a Python 2 environment
+# mxdoc.py - a sphinx extension, was not Python 3 compatible in the old versions
+# source activate mxnet_p27
+
+# Install dependencies
+sudo apt-get update
+sudo apt-get install -y \
+ apt-transport-https \
+ ca-certificates \
+ curl \
+ doxygen \
+ software-properties-common
+
+pip install --user \
+ beautifulsoup4 \
+ breathe \
+ CommonMark==0.5.4 \
+ h5py \
+ mock==1.0.1 \
+ pypandoc \
+ recommonmark==0.4.0 \
+ sphinx==1.5.6
+
+# Recommonmark/Sphinx errors: https://github.com/sphinx-doc/sphinx/issues/3800
+
+
+# Setup scala
+echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list
+sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
+sudo apt-get update
+sudo apt-get install -y \
+ sbt \
+ scala
+
+# Cleanup
+sudo apt autoremove -y
+
+# Make docs using the manual way
+# cd .. && make html USE_OPENMP=0
+# using the docker way
+# sudo make docs
+
diff --git a/docs/build_version_doc/update_all_version.sh b/docs/build_version_doc/update_all_version.sh
new file mode 100755
index 000000000000..e79b97117c5a
--- /dev/null
+++ b/docs/build_version_doc/update_all_version.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+
+# 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.
+
+# This script will update the html content from building
+# different tags.
+# It assumes you have already run build_all_version.sh for
+# the tags you want to update.
+
+# Takes three arguments:
+# * tag list - space delimited list of Github tags; Example: "1.1.0 1.0.0 master"
+# * default tag - which version should the site default to; Example: 1.0.0
+# * root URL - for the versions dropdown to change to production or dev server; Example: http://mxnet.incubator.apache.org/
+
+# Example Usage:
+# ./update_all_version.sh "1.1.0 1.0.0 master" 1.0.0 http://mxnet.incubator.apache.org/
+
+set -e
+set -x
+
+if [ -z "$1" ]
+ then
+ echo "Please provide a list of version tags you wish to run. Ex : \"1.1.0 1.0.0 master\""
+ exit 1
+ else
+ tag_list=$1
+fi
+
+if [ -z "$2" ]
+ then
+ echo "Please pick a version to use as a default for the website. Ex: 1.1.0"
+ exit 1
+ else
+ tag_default=$2
+fi
+
+if [ -z "$3" ]
+ then
+ echo "Please provide the root url for the site. Ex: http://mxnet.incubator.apache.org/"
+ exit 1
+ else
+ root_url=$3
+fi
+
+mxnet_folder="apache_mxnet"
+built="VersionedWeb"
+tag_file="tag_list.txt"
+
+if [ -f "$tag_file" ]; then
+ rm $tag_file
+fi
+
+# Write all version numbers into $tag_file for AddVersion.py to use later
+# Master is added by that script by default
+for tag in $tag_list; do
+ if [ $tag != 'master' ]
+ then
+ echo "$tag" >> "$tag_file"
+ fi
+done
+
+# Update the specified tags with the Versions dropdown
+for tag in $tag_list; do
+ # This Python script is expecting the tag_list.txt and it will use that as the entries to populate
+ python AddVersion.py --root_url "$root_url" --file_path "$built/versions/$tag" --current_version "$tag" || exit 1
+
+ if [ $tag != 'master' ]
+ then
+ python AddPackageLink.py --file_path "$built/versions/master/install/index.html" \
+ --current_version "$tag" || exit 1
+ fi
+
+ if [ $tag == $tag_default ]
+ then
+ cp -a "$built/versions/$tag/." "$built"
+ else
+ file_loc="$built/versions/$tag"
+ #rm -rf "$file_loc"
+ #mkdir "$file_loc"
+ #cp -a $mxnet_folder/docs/_build/html/. "$file_loc"
+ fi
+done
+
+echo "The output of this process can be found in the VersionedWeb folder."
+
diff --git a/example/image-classification/common/fit.py b/example/image-classification/common/fit.py
index 0e0cd521f28d..9412b6f9371b 100755
--- a/example/image-classification/common/fit.py
+++ b/example/image-classification/common/fit.py
@@ -237,6 +237,9 @@ def fit(args, network, data_loader, **kwargs):
if args.network == 'alexnet':
# AlexNet will not converge using Xavier
initializer = mx.init.Normal()
+ # VGG will not trend to converge using Xavier-Gaussian
+ elif 'vgg' in args.network:
+ initializer = mx.init.Xavier()
else:
initializer = mx.init.Xavier(
rnd_type='gaussian', factor_type="in", magnitude=2)
diff --git a/include/mxnet/tensor_blob.h b/include/mxnet/tensor_blob.h
index 168ddcca24b7..59c1eacb2c58 100755
--- a/include/mxnet/tensor_blob.h
+++ b/include/mxnet/tensor_blob.h
@@ -36,8 +36,18 @@
#include
#include
#include "./base.h"
+
namespace mxnet {
+// redefine DLPack enumeration to be backward compatible.
+constexpr const int kCPU = kDLCPU;
+constexpr const int kGPU = kDLGPU;
+// extension type code under TVM function.
+// Currently NNVM reserved 16 to 19 type code from TVM
+// 16, 17, 18 is used by NNVM compiler already.
+// Pick code 19 for MXNet NDArray
+constexpr const int kTVMNDArrayTypeCode = 19;
+
/* Forward declaration for friend declaration in TBlob */
class NDArray;
diff --git a/nnvm b/nnvm
index 7a052d678455..c342da72271c 160000
--- a/nnvm
+++ b/nnvm
@@ -1 +1 @@
-Subproject commit 7a052d678455f1c96538c1cc5a25f11115363558
+Subproject commit c342da72271c85e477480323f1d91997c6101ac0
diff --git a/python/mxnet/contrib/__init__.py b/python/mxnet/contrib/__init__.py
index 21c77719b70b..ad1010443f9e 100644
--- a/python/mxnet/contrib/__init__.py
+++ b/python/mxnet/contrib/__init__.py
@@ -28,3 +28,4 @@
from . import tensorboard
from . import text
+from . import onnx
diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py
new file mode 100644
index 000000000000..e5402002bd80
--- /dev/null
+++ b/python/mxnet/contrib/onnx/__init__.py
@@ -0,0 +1,18 @@
+# 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.
+
+from . import _import
diff --git a/python/mxnet/contrib/onnx/_import/__init__.py b/python/mxnet/contrib/onnx/_import/__init__.py
new file mode 100644
index 000000000000..748c1df19a54
--- /dev/null
+++ b/python/mxnet/contrib/onnx/_import/__init__.py
@@ -0,0 +1,43 @@
+# 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
+"""import function"""
+import onnx
+from .import_onnx import GraphProto
+
+def import_model(model_file):
+ """Imports the supplied ONNX model file into MXNet symbol and parameters.
+
+ Parameters
+ ----------
+ model_file : ONNX model file name
+
+ Returns
+ -------
+ sym : mx.symbol
+ Compatible mxnet symbol
+
+ params : dict of str to mx.ndarray
+ Dict of converted parameters stored in mx.ndarray format
+ """
+ graph = GraphProto()
+
+ # loads model file and returns ONNX protobuf object
+ model_proto = onnx.load(model_file)
+ sym, params = graph.from_onnx(model_proto.graph)
+ return sym, params
diff --git a/python/mxnet/contrib/onnx/_import/common.py b/python/mxnet/contrib/onnx/_import/common.py
new file mode 100644
index 000000000000..9154f009ffdd
--- /dev/null
+++ b/python/mxnet/contrib/onnx/_import/common.py
@@ -0,0 +1,131 @@
+# 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=invalid-name,no-self-use,too-many-branches,too-few-public-methods,too-many-arguments
+"""Shared functions and classes for frontends."""
+from __future__ import absolute_import as _abs
+from ....base import string_types
+
+class AttributeConverter(object):
+ """Common attribute converter. An AttributeConverter instance is a callable:
+ ```
+ attr_converter = AttributeConverter(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 provided, 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 occurred.
+ disables : list
+ A list of attributes that is disabled in mxnet. Raise warnings.
+ ignores : list
+ A list of attributes that is ignored in mxnet. 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._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/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py
new file mode 100644
index 000000000000..f67dcc3c05d6
--- /dev/null
+++ b/python/mxnet/contrib/onnx/_import/import_helper.py
@@ -0,0 +1,245 @@
+# 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=invalid-name
+"""Operator attributes conversion"""
+from .common import AttributeConverter as AttrCvt
+
+def _revert_caffe2_pad(attr):
+ """Removing extra padding from Caffe2."""
+ 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_constraint():
+ """checking dimensions for conv, deconv, pooling operators"""
+ def _dim_check(attrs):
+ if len(attrs['kernel_shape']) == 2:
+ return True
+ return False
+ return _dim_check, "Only 2d kernel supported."
+
+def _elemwise(name):
+ """converting attributes for add operator"""
+ return AttrCvt(
+ op_name=_math_name_picker(name),
+ disables=['axis'],
+ ignores=['broadcast'])
+
+def _pooling(name):
+ """converting attributes for pooling operator"""
+ return AttrCvt(
+ op_name='Pooling',
+ transforms={
+ 'kernel_shape': 'kernel',
+ 'strides': 'stride',
+ 'pads': 'pad'},
+ # pooling convention full to match caffe2
+ extras={'pool_type': name, 'pooling_convention':'valid'},
+ custom_check=_dimension_constraint())
+
+def _conv():
+ """converting attributes for convolution operator"""
+ return AttrCvt(
+ op_name='Convolution',
+ transforms={
+ 'kernel_shape': 'kernel',
+ 'strides': 'stride',
+ 'dilations': ('dilate', (0, 0)),
+ 'pads': ('pad', (0, 0), _revert_caffe2_pad),
+ 'group': ('num_group', 1)},
+ custom_check=_dimension_constraint())
+
+def _conv_transpose():
+ """converting attributes for deconvolution operator"""
+ return AttrCvt(
+ op_name='Deconvolution',
+ transforms={
+ 'kernel_shape': 'kernel',
+ 'strides': 'stride',
+ 'dilations': ('dilate', (0, 0)),
+ 'pads': ('pad', (0, 0), _revert_caffe2_pad),
+ 'group': ('num_group', 1)},
+ disables=['output_shape'],
+ custom_check=_dimension_constraint())
+
+def _batch_norm():
+ """converting attributes for BatchNorm operator"""
+ return AttrCvt(
+ op_name='BatchNorm',
+ transforms={'epsilon': 'eps'},
+ extras={'cudnn_off': 1},
+ ignores=['spatial', 'is_test', 'consumed_inputs'])
+
+def _activation(name):
+ """converting attributes for LeakyRelu operator"""
+ return AttrCvt(
+ op_name='LeakyReLU',
+ transforms={
+ 'alpha':'slope'},
+ extras={'act_type': name})
+
+def _pad_sequence_fix(attr, kernelDim=None):
+ """Changing onnx's pads sequence to match with mxnet's pad_width
+ mxnet: (x1_begin, x1_end, ... , xn_begin, xn_end)
+ onnx: (x1_begin, x2_begin, ... , xn_end, xn_end)"""
+ new_attr = ()
+ if len(attr) % 2 == 0:
+ for index in range(int(len(attr) / 2)):
+ new_attr = new_attr + attr[index::int(len(attr) / 2)]
+ # Making sure pad values are in the attr for all axes.
+ if kernelDim is not None:
+ while len(new_attr) < kernelDim*2:
+ new_attr = new_attr + (0, 0)
+ return new_attr
+
+def _pad():
+ """converting attributes for Pad operator"""
+ return AttrCvt(
+ op_name='pad',
+ transforms={
+ 'pads': ('pad_width', (0, 0, 0, 0, 0, 0, 0, 0), _pad_sequence_fix),
+ 'value': 'constant_value'})
+
+def _global_pooling(name):
+ """Requires kernel attribute which is not present in onnx currently.
+ So for now giving default kernel."""
+ return AttrCvt(
+ op_name='Pooling',
+ extras={'global_pool': True,
+ 'kernel': (1, 1),
+ 'pool_type': name})
+
+def _upsample_scale_fix(attr):
+ """Scale attribute conversion from float to int"""
+ return int(attr)
+
+def _upsample_restrict_mode(attr):
+ """Mxnet's current UpSampling operator doesn't work well in bilinear mode.
+ New operator is coming in this PR https://github.com/apache/incubator-mxnet/pull/9688/
+ Issue to track this: https://github.com/onnx/onnx-mxnet/issues/33
+ For now, only nearest mode is enabled."""
+ if attr.decode() != 'nearest':
+ raise ValueError("Only nearest mode is supported: {}".format(attr))
+ return attr.decode()
+
+def _upsample(name):
+ """converting attributes for UpSampling operator"""
+ return AttrCvt(
+ op_name=name,
+ transforms={'height_scale': ('scale', 1, _upsample_scale_fix),
+ 'mode': ('sample_type', 'nearest', _upsample_restrict_mode),
+ 'width_scale': ('scale', 1, _upsample_scale_fix)})
+
+# _convert_map defines maps of name to converter functor(callable)
+_convert_map = {
+ # defs/experimental
+ 'FC' : AttrCvt('FullyConnected', ignores=['axis', 'axis_w']),
+
+ # defs/generator
+ 'Constant': AttrCvt('identity'),
+ 'RandomUniform' : AttrCvt('random_uniform', ignores=['seed']),
+ 'RandomNormal' : AttrCvt('random_normal', {'mean':'loc'}, ignores=['seed']),
+ 'RandomUniformLike' : AttrCvt('random_uniform', ignores=['seed']),
+ 'RandomNormalLike': AttrCvt('random_normal', {'mean':'loc'}, ignores=['seed']),
+
+ # defs/logical
+
+ # defs/math
+ 'Add' : _elemwise('add'),
+ 'Sub' : _elemwise('sub'),
+ 'Mul' : _elemwise('mul'),
+ 'Div' : _elemwise('div'),
+ 'Neg' : AttrCvt('negative'),
+ 'Abs' : AttrCvt('abs'),
+ 'Reciprocal' : AttrCvt('reciprocal'),
+ 'Floor' : AttrCvt('floor'),
+ 'Ceil' : AttrCvt('ceil'),
+ 'Sqrt' : AttrCvt('sqrt'),
+ 'Gemm' : AttrCvt('linalg_gemm', {'transA':'transpose_a', 'transB':'transpose_b'},
+ ignores=['broadcast']),
+ 'Relu' : AttrCvt('relu'),
+ 'LeakyRelu' : AttrCvt('LeakyReLU', {'alpha': 'slope'}),
+ # 'Selu'
+ 'Elu' : _activation('elu'),
+ 'Exp' : AttrCvt('exp'),
+ 'Log' : AttrCvt('log'),
+ 'Tanh' : AttrCvt('tanh'),
+ 'Pow' : AttrCvt('pow', {'exponent':'exp'}),
+ 'Dot' : AttrCvt('dot'),
+ 'MatMul' : AttrCvt('linalg_gemm2'),
+ # 'PRelu'
+ 'Sigmoid' : AttrCvt('sigmoid'),
+ 'Max' : AttrCvt('maximum'), #elemwise maximum
+ 'Min' : AttrCvt('minimum'), #elemwise minimum
+ 'Sum' : AttrCvt('add_n'), #elemwise sum
+ # softmax default axis is different in onnx
+ 'Softmax' : AttrCvt('softmax', extras={'axis': 1}),
+
+ # defs/nn
+ 'AveragePool' : _pooling('avg'),
+ 'MaxPool' : _pooling('max'),
+ 'Conv' : _conv(),
+ 'ConvTranspose' : _conv_transpose(),
+ 'GlobalAveragePool': _global_pooling('avg'),
+ 'GlobalMaxPool' : _global_pooling('max'),
+ 'BatchNormalization': _batch_norm(),
+ 'SpatialBN' : _batch_norm(),
+ 'Dropout' : AttrCvt('Dropout', {'ratio': 'p'}, ignores=['is_test']),
+ 'Flatten' : AttrCvt('flatten'),
+ 'LRN' : AttrCvt('LRN', {'bias': 'knorm', 'size' : 'nsize'}),
+ # defs/reduction
+ 'ReduceMax' : AttrCvt('max', {'axes': 'axis'}),
+ 'ReduceMin' : AttrCvt('min', {'axes': 'axis'}),
+ 'ReduceSum' : AttrCvt('sum', {'axes': 'axis'}),
+ 'ReduceMean' : AttrCvt('mean', {'axes': 'axis'}),
+ 'ReduceProd' : AttrCvt('prod', {'axes': 'axis'}),
+ # 'ReduceLogSumExp'
+ 'ArgMax' : AttrCvt('argmax'),
+ 'ArgMin' : AttrCvt('argmin'),
+
+ # defs/tensor
+ 'Cast' : AttrCvt('cast', {'to': 'dtype'}),
+ 'Reshape' : AttrCvt('reshape'),
+ 'Concat' : AttrCvt('concat', {'axis': 'dim'}),
+ 'Split' : AttrCvt('split', {'split': 'num_outputs'}),
+ 'Pad' : _pad(),
+ 'Slice' : AttrCvt('slice_axis', {'axes': 'axis', 'ends': 'end', 'starts': 'begin'}),
+ 'Transpose' : AttrCvt('transpose', {'perm': 'axes'}),
+ 'Squeeze' : AttrCvt('split', {'axes': 'axis'}),
+ # 'Gather'
+ 'Upsample' : _upsample('UpSampling')
+}
diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py
new file mode 100644
index 000000000000..232ec6673d62
--- /dev/null
+++ b/python/mxnet/contrib/onnx/_import/import_onnx.py
@@ -0,0 +1,299 @@
+# 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=invalid-name,too-many-locals,no-self-use
+""" Support import export formats."""
+from __future__ import absolute_import as _abs
+from .... import symbol
+from .... import ndarray as nd
+from .import_helper import _convert_map, _pad_sequence_fix
+
+def _convert_operator(op_name, attrs, convert_map=None):
+ """Convert from onnx operator to mxnet operator.
+ The converter must specify conversions explicitly 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 mxnet, callable are functions which
+ take attrs and return (new_op_name, new_attrs)
+
+ Returns
+ -------
+ (op_name, attrs)
+ Converted (op_name, attrs) for mxnet.
+ """
+ convert_map = convert_map if convert_map else _convert_map
+ if op_name in convert_map:
+ op_name, attrs = convert_map[op_name](attrs)
+ else:
+ raise NotImplementedError("Operator {} not implemented.".format(op_name))
+ op = getattr(symbol, op_name, None)
+ if not op:
+ raise RuntimeError("Unable to map op_name {} to sym".format(op_name))
+ return op, attrs
+
+class GraphProto(object): # pylint: disable=too-few-public-methods
+ """A helper class for handling mxnet symbol 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 symbol 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 :symbol.Symbol
+ The returned mxnet symbol
+ params : dict
+ A dict of name: nd.array pairs, used as pretrained weights
+ """
+ # parse network inputs, 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)
+
+ # converting GraphProto message
+ for i in graph.input:
+ 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] = symbol.Variable(name=name_param,
+ shape=self._params[name_param].shape)
+ self._renames[i.name] = name_param
+ else:
+ name_input = 'input_{}'.format(self._num_input)
+ self._num_input += 1
+ self._nodes[name_input] = symbol.Variable(name=name_input)
+ self._renames[i.name] = name_input
+
+ # constructing nodes, nodes are stored as directed acyclic graph
+ # converting NodeProto message
+ for node in graph.node:
+ op_name = node.op_type
+ node_name = node.name.strip()
+ node_name = node_name if node_name else None
+ onnx_attr = self._parse_attr(node.attribute)
+ new_op, mx_attr = _convert_operator(op_name, onnx_attr)
+ inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
+
+ # some workarounds for inconsistencies in onnx and mxnet conventions.
+ mx_attr = self._fix_bias(new_op, mx_attr, len(inputs))
+ mx_attr = self._fix_channels(new_op, mx_attr, list(node.input))
+ self._fix_bias_shape(node.op_type, node.input, onnx_attr)
+
+ # calling again to get new symbols after some workarounds
+ inputs = [self._nodes[self._renames.get(i, i)] for i in node.input]
+
+ # onnx's Gemm operator also supports broadcasting C input which
+ # mxnet's equivalent linalg_gemm doesn't. So using combination of
+ # transpose and FullyConnected operators.
+ if op_name == 'Gemm':
+ new_op, inputs, mx_attr = self._fix_gemm('FullyConnected', inputs, onnx_attr)
+
+ # onnx slice works on multiple axes whereas mxnet's slice_axis is for single axis
+ if op_name == 'Slice':
+ op = self._fix_slice(inputs, mx_attr)
+ elif op_name == 'AveragePool' and onnx_attr.get('pads') is not None or \
+ op_name == 'MaxPool' and onnx_attr.get('pads') is not None:
+ op = self._fix_pooling(op_name, inputs, onnx_attr)
+ elif op_name == 'Squeeze':
+ op = self._fix_squeeze(inputs, mx_attr)
+ else:
+ op = new_op(name=node_name, *inputs, **mx_attr)
+
+ node_output = self._fix_outputs(op_name, node.output)
+
+ assert len(node_output) == len(op.list_outputs()), (
+ "Number of output mismatch {} vs {} in {}.".format(
+ len(node_output), len(op.list_outputs()), 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.name] for i in graph.output]
+ if len(out) > 1:
+ out = symbol.Group(out)
+ else:
+ out = out[0]
+ return out, self._params
+
+ def _fix_pooling(self, op_name, inputs, new_attr):
+ """onnx pooling operator supports asymmetrical padding
+ Adding pad operator before pooling in mxnet to work with onnx"""
+ pool_type = 'avg' if op_name == 'AveragePool' else 'max'
+ stride = new_attr.get('strides')
+ kernel = new_attr.get('kernel_shape')
+ padding = new_attr.get('pads')
+ pad_width = (0, 0, 0, 0) + _pad_sequence_fix(padding, len(kernel))
+ new_pad_op = symbol.pad(inputs[0], mode='constant', pad_width=pad_width)
+ new_pooling_op = symbol.Pooling(new_pad_op, pool_type=pool_type,
+ stride=stride, kernel=kernel)
+ return new_pooling_op
+
+ def _fix_slice(self, inputs, new_attr):
+ """onnx slice provides slicing on multiple axis. Adding multiple slice_axis operator
+ for multiple axes from mxnet"""
+ begin = new_attr.get('begin')
+ end = new_attr.get('end')
+ axes = new_attr.get('axis', tuple(range(len(begin))))
+ slice_op = symbol.slice_axis(inputs[0], axis=axes[0], begin=begin[0], end=end[0])
+ if len(axes) > 1:
+ for i, axis in enumerate(axes):
+ slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i])
+ return slice_op
+
+ def _fix_squeeze(self, inputs, new_attr):
+ """
+ MXNet doesnt have a squeeze operator.
+ Using "split" to perform similar operation.
+ "split" can be slower compared to "reshape".
+ This can have performance impact.
+ TODO: Remove this implementation once mxnet adds the support.
+ """
+ axes = new_attr.get('axis')
+ op = symbol.split(inputs[0], axis=axes[0], num_outputs=1, squeeze_axis=1)
+ for i in axes[1:]:
+ op = symbol.split(op, axis=i-1, num_outputs=1, squeeze_axis=1)
+ return op
+
+ def _fix_gemm(self, op_name, inputs, old_attr):
+ """Using FullyConnected operator in place of linalg_gemm to perform same operation"""
+ op = getAttr(symbol, op_name, None)
+ alpha = float(old_attr.get('alpha', 1.0))
+ beta = float(old_attr.get('beta', 1.0))
+ transA = int(old_attr.get('transA', 0))
+ transB = int(old_attr.get('transB', 0))
+ if transA:
+ inputs[0] = symbol.transpose(inputs[0], axes=(1, 0))
+ if not transB:
+ inputs[1] = symbol.transpose(inputs[1], axes=(1, 0))
+ new_inputs = [alpha*inputs[0], inputs[1], beta*inputs[2]]
+ new_attr = {'num_hidden' : self._params[inputs[2].name].shape[0]}
+ return op, new_inputs, new_attr
+
+ 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 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):
+ attrs[a.name] = getattr(a, f)
+ for f in ['tensors', 'graphs']:
+ if list(getattr(a, f)):
+ raise NotImplementedError("Filed {} is not supported in mxnet.".format(f))
+ if a.name not in attrs:
+ raise ValueError("Cannot parse attribute: \n{}\n.".format(a))
+ return attrs
+
+ def _fix_outputs(self, op, outputs):
+ """A workaround 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 workaround 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 [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]:
+ return attrs
+ if num_inputs == 3:
+ attrs['no_bias'] = False
+ elif num_inputs == 2:
+ attrs['no_bias'] = True
+ else:
+ raise ValueError("Unexpected number of inputs for: {}".format(op))
+ return attrs
+
+
+ def _fix_bias_shape(self, op_name, inputs, attrs):
+ """A workaround to reshape bias term to (1, num_channel)."""
+ if (op_name == 'Add' or op_name == 'Mul') and (int(len(self._params)) > 0) and \
+ ('broadcast' in attrs and attrs['broadcast'] == 1):
+ 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 = nd.array(bias.asnumpy().reshape((1, -1, 1, 1)))
+ # broadcast_add expects shape with sym.variable
+ self._nodes[bias_name] = symbol.Variable(name=bias_name, shape=bias.shape)
+ self._params[bias_name] = bias
+
+
+ def _fix_channels(self, op, attrs, inputs):
+ """A workaround for getting 'channels' or 'units' since onnx don't provide
+ these attributes. We check the shape of weights provided to get the number.
+ """
+ if op not in [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]:
+ 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 [symbol.FullyConnected]:
+ attrs['num_hidden'] = channels
+ else:
+ attrs['num_filter'] = channels
+ return attrs
diff --git a/python/mxnet/metric.py b/python/mxnet/metric.py
index 0a02b80a1c06..ddffc01bd238 100644
--- a/python/mxnet/metric.py
+++ b/python/mxnet/metric.py
@@ -510,28 +510,27 @@ def update_binary_stats(self, label, pred):
if len(numpy.unique(label)) > 2:
raise ValueError("%s currently only supports binary classification."
% self.__class__.__name__)
+ pred_true = (pred_label == 1)
+ pred_false = 1 - pred_true
+ label_true = (label == 1)
+ label_false = 1 - label_true
- for y_pred, y_true in zip(pred_label, label):
- if y_pred == 1 and y_true == 1:
- self.true_positives += 1.
- elif y_pred == 1 and y_true == 0:
- self.false_positives += 1.
- elif y_pred == 0 and y_true == 1:
- self.false_negatives += 1.
- else:
- self.true_negatives += 1.
+ self.true_positives += (pred_true * label_true).sum()
+ self.false_positives += (pred_true * label_false).sum()
+ self.false_negatives += (pred_false * label_true).sum()
+ self.true_negatives += (pred_false * label_false).sum()
@property
def precision(self):
if self.true_positives + self.false_positives > 0:
- return self.true_positives / (self.true_positives + self.false_positives)
+ return float(self.true_positives) / (self.true_positives + self.false_positives)
else:
return 0.
@property
def recall(self):
if self.true_positives + self.false_negatives > 0:
- return self.true_positives / (self.true_positives + self.false_negatives)
+ return float(self.true_positives) / (self.true_positives + self.false_negatives)
else:
return 0.
diff --git a/python/mxnet/ndarray/ndarray.py b/python/mxnet/ndarray/ndarray.py
index 59389ddb220d..5ac279635a1d 100644
--- a/python/mxnet/ndarray/ndarray.py
+++ b/python/mxnet/ndarray/ndarray.py
@@ -174,8 +174,15 @@ class NDArray(NDArrayBase):
__slots__ = []
# make numpy functions return NDArray instead of numpy object array
__array_priority__ = 1000.0
+ # Extension type code for TVM function.
+ # See C++ side of definition(kTVMNDArrayTypeCode) at include/mxmet/tensor_blob.h
+ _tvm_tcode = 19
# pylint: disable= no-member, undefined-variable
+ @property
+ def _tvm_handle(self):
+ return self.handle.value
+
def __repr__(self):
"""Returns a string representation of the array."""
shape_info = 'x'.join(['%d' % x for x in self.shape])
diff --git a/python/setup.py b/python/setup.py
index cf94adf982d5..1ef14d95a0d1 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -28,7 +28,7 @@
else:
from setuptools import setup
from setuptools.extension import Extension
- kwargs = {'install_requires': ['numpy<=1.13.3,>=1.8.2', 'requests==2.18.4', 'graphviz==0.8.1'], 'zip_safe': False}
+ kwargs = {'install_requires': ['numpy<=1.13.3,>=1.8.2', 'requests==2.18.4', 'graphviz==0.8.1', 'onnx>=1.0.1'], 'zip_safe': False}
from setuptools import find_packages
with_cython = False
diff --git a/src/nnvm/tvm_bridge.cc b/src/nnvm/tvm_bridge.cc
new file mode 100644
index 000000000000..06929984640d
--- /dev/null
+++ b/src/nnvm/tvm_bridge.cc
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file tvm_bridge.cc
+ * \brief Bridge to run TVM's PackedFunc in MXNet's async engine.
+ *
+ * This bridge is mainly used to expose MXNet's async engine push to
+ * TVM. It only uses TVM runtime in aheader only mode, which means
+ * there is no link dependencies.
+ *
+ * Support for TVM is optional even when this code
+ * is always compiled and built with the project.
+ * We choose this strategy because we do not yet want
+ * llvm as dependency(which TVM uses). So instead we expose hook
+ * to TVM and let user use this feature when they have TVM installed.
+ *
+ * We do require TVM and MXNet to be built with same C++ ABI of std::function
+ */
+#define TVM_RUNTIME_HEADER_ONLY 1
+#include
+#include
+#include
+#include
+
+#include
+
+namespace mxnet {
+
+using tvm::runtime::PackedFunc;
+using tvm::runtime::TVMArgs;
+using tvm::runtime::TVMRetValue;
+
+/*!
+ * \brief Async functor object
+ * calling argument of the function.
+ */
+class TVMFunctor {
+ public:
+ // constructor
+ explicit TVMFunctor(PackedFunc func, PackedFunc fset_stream)
+ : func_(func), fset_stream_(fset_stream) {}
+
+ void Init(const TVMArgs& args,
+ const std::vector& const_loc,
+ std::vector* const_vars,
+ std::vector* mutate_vars) {
+ values_.clear();
+ type_codes_.clear();
+ values_.insert(values_.end(), args.values, args.values + args.size());
+ type_codes_.insert(
+ type_codes_.end(), args.type_codes, args.type_codes + args.size());
+
+ size_t const_loc_ptr = 0;
+ for (int i = 0; i < args.size(); ++i) {
+ if (args.type_codes[i] == kTVMNDArrayTypeCode) {
+ const NDArray& nd =
+ static_cast(args.values[i].v_handle)[0];
+ // We cannot set the value until
+ type_codes_[i] = kArrayHandle;
+ array_data_.push_back(nd);
+ array_loc_.push_back(i);
+ // check if there is read or mutate
+ // by default assume we mutate the array.
+ if (const_loc_ptr < const_loc.size() &&
+ i == const_loc[const_loc_ptr]) {
+ const_vars->push_back(nd.var());
+ ++const_loc_ptr;
+ } else {
+ mutate_vars->push_back(nd.var());
+ }
+ } else {
+ CHECK_LT(args.type_codes[i], kTVMType)
+ << "Only allow POD type in mxnet async call";
+ }
+ }
+ }
+
+ Context ctx() {
+ return array_data_[0].ctx();
+ }
+
+ void Run(const RunContext& rctx) {
+ // setup DLTensor
+ for (size_t i = 0; i < array_loc_.size(); ++i) {
+ values_[array_loc_[i]].v_handle =
+ const_cast(&(array_data_[i].data().dltensor()));
+ }
+ // run the packed function
+ TVMRetValue rv;
+ TVMArgs args(&values_[0], &type_codes_[0], values_.size());
+ if (ctx().dev_type == Context::kGPU) {
+#if MXNET_USE_CUDA
+ // pass stream via last argument.
+ void* strm = static_cast(rctx.get_stream()->stream_);
+ int dev_type = kDLGPU;
+ fset_stream_(dev_type, rctx.ctx.dev_id, strm);
+ func_.CallPacked(args, &rv);
+ fset_stream_(dev_type, rctx.ctx.dev_id, nullptr);
+#else
+ LOG(FATAL) << "Please compile with CUDA enabled for cuda features";
+#endif
+ } else {
+ func_.CallPacked(args, &rv);
+ }
+ }
+
+ private:
+ /*! \brief The function */
+ PackedFunc func_;
+ /*! \brief Set stream */
+ PackedFunc fset_stream_;
+ /*! \brief Values field */
+ std::vector values_;
+ /*! \brief type code field */
+ std::vector type_codes_;
+ /*! \brief arrays field */
+ std::vector array_data_;
+ /*! \brief position of array in arguments */
+ std::vector array_loc_;
+};
+
+
+// Wrap a TVM function to a function that invokes MXNet's Engine
+// It does two things: call the engine properly
+// set up the NDArray to DLTensor during invocation.
+void WrapAsyncCall(TVMArgs wrap_args, TVMRetValue* wrap_rv) {
+ PackedFunc f = wrap_args[0];
+ PackedFunc fset_stream = wrap_args[1];
+ int num_const = wrap_args[2];
+
+ // sorted position of constant arguments
+ std::vector const_loc;
+ for (int i = 0; i < num_const; ++i) {
+ const_loc.push_back(wrap_args[i + 3].operator int());
+ }
+ std::sort(const_loc.begin(), const_loc.end());
+ // wrapped function
+ // This is the function that called by the user.
+ auto wrapped = [f, fset_stream, const_loc](TVMArgs args, TVMRetValue* rv) {
+ std::shared_ptr func =
+ std::make_shared(f, fset_stream);
+ std::vector const_vars, mutate_vars;
+ func->Init(args, const_loc, &const_vars, &mutate_vars);
+ Engine *engine = Engine::Get();
+ engine->DeduplicateVarHandle(&const_vars, &mutate_vars);
+ engine->PushSync([func](RunContext ctx) {
+ func->Run(ctx);
+ }, func->ctx(), const_vars, mutate_vars);
+ };
+ *wrap_rv = PackedFunc(wrapped);
+}
+
+} // namespace mxnet
+
+// C callback that can be used by TVM to extract
+// the WrapAsyncCall function.
+extern "C" MXNET_DLL int MXTVMBridge(TVMFunctionHandle pregister) {
+ using tvm::runtime::PackedFunc;
+ const PackedFunc& fregister =
+ *static_cast(pregister);
+ fregister("WrapAsyncCall", PackedFunc(mxnet::WrapAsyncCall));
+ return 0;
+}
diff --git a/src/operator/regression_output.cc b/src/operator/regression_output.cc
index 7b0fbae3bccb..0b8ce69062bd 100644
--- a/src/operator/regression_output.cc
+++ b/src/operator/regression_output.cc
@@ -23,6 +23,8 @@
*/
#include "./regression_output-inl.h"
+#include "./elemwise_op_common.h"
+
#define MXNET_OPERATOR_REGISTER_REGRESSION_FWD(__name$, __kernel$, __bwdop$) \
NNVM_REGISTER_OP(__name$) \
@@ -33,6 +35,7 @@
return std::vector{"data", "label"}; \
}) \
.set_attr("FInferShape", RegressionOpShape) \
+ .set_attr("FInferType", ElemwiseType<2, 1>) \
.set_attr("FGradient", RegressionOpGrad{__bwdop$}) \
.set_attr("FInplaceOption", \
[](const NodeAttrs& attrs){ \
@@ -48,6 +51,7 @@
.set_num_inputs(2) \
.set_num_outputs(2) \
.set_attr_parser(ParamParser) \
+ .set_attr("FInferType", ElemwiseType<2, 2>) \
.set_attr("TIsBackward", true) \
.set_attr("FInplaceOption", \
[](const NodeAttrs& attrs){ \
diff --git a/tests/ci_build/Dockerfile.gpu b/tests/ci_build/Dockerfile.gpu
index 2483e62b99b5..bd1a00839167 100644
--- a/tests/ci_build/Dockerfile.gpu
+++ b/tests/ci_build/Dockerfile.gpu
@@ -12,3 +12,9 @@ COPY install/ubuntu_install_r.sh /install/
RUN /install/ubuntu_install_r.sh
COPY install/ubuntu_install_perl.sh /install/
RUN /install/ubuntu_install_perl.sh
+
+COPY install/ubuntu_install_llvm.sh /install/
+RUN /install/ubuntu_install_llvm.sh
+
+COPY install/ubuntu_install_tvm.sh /install/
+RUN /install/ubuntu_install_tvm.sh
diff --git a/tests/ci_build/install/ubuntu_install_llvm.sh b/tests/ci_build/install/ubuntu_install_llvm.sh
new file mode 100755
index 000000000000..d3282e7a5fce
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_llvm.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# 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.
+
+
+
+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
+
+wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+apt-get update && apt-get install -y --force-yes llvm-5.0
diff --git a/tests/ci_build/install/ubuntu_install_tvm.sh b/tests/ci_build/install/ubuntu_install_tvm.sh
new file mode 100755
index 000000000000..2729c7fe3bee
--- /dev/null
+++ b/tests/ci_build/install/ubuntu_install_tvm.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+# 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.
+
+# Build and install TVM
+cd /tmp
+git clone https://github.com/dmlc/tvm/ --recursive
+cd tvm
+
+# This is a stable tag that support MXNet TVM bridge.
+# We use this since support for mxnet bridge just checked
+# into master and there is yet a version tag
+git checkout 30eaf463e34d7c301357c31a010945d11df16537
+
+cp make/config.mk
+echo USE_CUDA=1 >> config.mk
+echo LLVM_CONFIG=llvm-config-5.0 >> config.mk
+echo USE_RPC=1 >> config.mk
+echo USE_GRAPH_RUNTIME=1 >> config.mk
+echo CUDA_PATH=/usr/local/cuda >> config.mk
+make -j`nproc`
+
+cd python
+python setup.py install
+cd -
+
+cd topi/python
+python setup.py install
+cd -
diff --git a/tests/python/gpu/test_tvm_bridge.py b/tests/python/gpu/test_tvm_bridge.py
new file mode 100644
index 000000000000..292b9d91e5f7
--- /dev/null
+++ b/tests/python/gpu/test_tvm_bridge.py
@@ -0,0 +1,64 @@
+# 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.
+
+"""Test TVM bridge, only enable this when TVM is available"""
+import logging
+import mxnet as mx
+import numpy as np
+
+def test_tvm_bridge():
+ # only enable test if TVM is available
+ try:
+ import tvm
+ import tvm.contrib.mxnet
+ import topi
+ except ImportError:
+ logging.warn("TVM bridge test skipped because TVM is missing...")
+ return
+
+ def check(target):
+ shape = (20,)
+ scale = tvm.var("scale", dtype="float32")
+ x = tvm.placeholder(shape)
+ y = tvm.placeholder(shape)
+ z = tvm.compute(shape, lambda i: x[i] + y[i])
+ zz = tvm.compute(shape, lambda *i: z(*i) * scale)
+ ctx = mx.gpu(0) if target == "cuda" else mx.cpu(0)
+ target = tvm.target.create(target)
+
+ # build the function
+ with target:
+ s = topi.generic.schedule_injective(zz)
+ f = tvm.build(s, [x, y, zz, scale])
+
+ # get a mxnet version
+ mxf = tvm.contrib.mxnet.to_mxnet_func(f, const_loc=[0, 1])
+ 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)
+ np.testing.assert_allclose(
+ zz.asnumpy(), (xx.asnumpy() + yy.asnumpy()) * 10)
+
+ check("llvm")
+ check("cuda")
+
+
+
+if __name__ == "__main__":
+ test_tvm_bridge()
diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/onnx_test_utils/backend.py
new file mode 100644
index 000000000000..33f266598284
--- /dev/null
+++ b/tests/python/onnx_test_utils/backend.py
@@ -0,0 +1,183 @@
+# 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=too-many-locals,invalid-name
+"""backend wrapper for onnx test infrastructure"""
+from collections import namedtuple
+import mxnet as mx
+from onnx import helper, TensorProto
+from onnx.backend.base import Backend
+from mxnet.contrib.onnx._import.import_onnx import GraphProto
+from .backend_rep import MXNetBackendRep
+
+# Using these functions for onnx test infrastructure.
+# Implemented by following onnx docs guide:
+# https://github.com/onnx/onnx/blob/master/docs/Implementing%20an%20ONNX%20backend.md
+# MXNetBackend class will take an ONNX model with inputs, perform a computation,
+# and then return the output.
+
+class MXNetBackend(Backend):
+ """MXNet backend for ONNX"""
+
+ @staticmethod
+ def make_graph(node, inputs):
+ """ Created ONNX GraphProto from node"""
+ initializer = []
+ tensor_input_info = []
+ tensor_output_info = []
+
+ # Adding input tensor info.
+ for index in range(len(node.input)):
+ tensor_input_info.append(
+ helper.make_tensor_value_info(str(node.input[index]), TensorProto.FLOAT, [1]))
+
+ # Creating an initializer for Weight params.
+ # Assumes that weight params is named as 'W'.
+ # TODO: Handle multiple weight params.
+ # TODO: Add for "bias" if needed
+ if node.input[index] == 'W':
+ dim = inputs[index].shape
+ param_tensor = helper.make_tensor(
+ name=node.input[index],
+ data_type=TensorProto.FLOAT,
+ dims=dim,
+ vals=inputs[index].flatten())
+
+ initializer.append(param_tensor)
+
+ # Adding output tensor info.
+ for index in range(len(node.output)):
+ tensor_output_info.append(
+ helper.make_tensor_value_info(str(node.output[index]), TensorProto.FLOAT, [1]))
+
+ # creating graph proto object.
+ graph_proto = helper.make_graph(
+ [node],
+ "test",
+ tensor_input_info,
+ tensor_output_info,
+ initializer=initializer)
+
+ return graph_proto
+
+ @classmethod
+ def run_node(cls, node, inputs, device='CPU'):
+ """Running individual node inference on mxnet engine and
+ return the result to onnx test infrastructure.
+
+ Parameters
+ ----------
+ node : onnx node object
+ loaded onnx node (individual layer)
+ inputs : numpy array
+ input to run a node on
+ device : 'CPU'
+ device to run a node on
+
+ Returns
+ -------
+ params : numpy array
+ result obtained after running the operator
+ """
+ graph = GraphProto()
+ sym, params = graph.from_onnx(MXNetBackend.make_graph(node, inputs))
+ data_names = [i for i in sym.get_internals().list_inputs() if i[:-1] == "input_"]
+ data_shapes = []
+ dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean',
+ 'ReduceProd', 'ReduceSum', 'Slice', 'Pad',
+ 'Squeeze', 'Upsample', 'Reshape', 'Conv'])
+
+ # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs.
+ for idx, input_name in enumerate(data_names):
+ batch_size = 1
+ if len(inputs[idx].shape) < 4 and len(inputs) > 1 and \
+ len(set(x.shape[0] for x in inputs)) != 1:
+ tuples = ((batch_size,), inputs[idx].shape)
+ new_shape = sum(tuples, ())
+ data_shapes.append((input_name, new_shape))
+ else:
+ data_shapes.append((input_name, inputs[idx].shape))
+
+ # create module, passing cpu context
+ if device == 'CPU':
+ ctx = mx.cpu()
+ else:
+ raise NotImplementedError("Only CPU context is supported for now")
+
+ # create a module
+ mod = mx.mod.Module(symbol=sym, data_names=data_names, context=ctx, label_names=None)
+ mod.bind(for_training=False, data_shapes=data_shapes, label_shapes=None)
+
+ # initializing parameters for calculating result of each individual node
+ if int(len(params)) > 0:
+ mod.set_params(arg_params=params, aux_params=params)
+ else:
+ mod.init_params()
+
+ batch = namedtuple('Batch', ['data'])
+
+ data_forward = []
+ for idx, input_name in enumerate(data_names):
+ # slice and pad operator tests needs 1 less dimension in forward pass
+ # otherwise it will throw an error.
+ # for squeeze operator, need to retain shape of input as provided
+ val = inputs[idx]
+ if node.op_type in dim_change_op_types:
+ data_forward.append(mx.nd.array(val))
+ else:
+ data_forward.append(mx.nd.array([val]))
+
+ mod.forward(batch(data_forward))
+ result = mod.get_outputs()[0].asnumpy()
+ if node.op_type in dim_change_op_types:
+ return [result]
+ return result
+
+ @classmethod
+ def prepare(cls, model, device='CPU', **kwargs):
+ """For running end to end model(used for onnx test backend)
+
+ Parameters
+ ----------
+ model : onnx ModelProto object
+ loaded onnx graph
+ device : 'CPU'
+ specifying device to run test on
+ kwargs :
+ other arguments
+
+ Returns
+ -------
+ MXNetBackendRep : object
+ Returns object of MXNetBackendRep class which will be in turn
+ used to run inference on the input model and return the result for comparison.
+ """
+ graph = GraphProto()
+ sym, params = graph.from_onnx(model.graph)
+ return MXNetBackendRep(sym, params, device)
+
+ @classmethod
+ def supports_device(cls, device):
+ """Supports only CPU for testing"""
+ return device == 'CPU'
+
+prepare = MXNetBackend.prepare
+
+run_node = MXNetBackend.run_node
+
+supports_device = MXNetBackend.supports_device
diff --git a/tests/python/onnx_test_utils/backend_rep.py b/tests/python/onnx_test_utils/backend_rep.py
new file mode 100644
index 000000000000..690054238667
--- /dev/null
+++ b/tests/python/onnx_test_utils/backend_rep.py
@@ -0,0 +1,75 @@
+# 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=too-few-public-methods
+"""backend rep for onnx test infrastructure"""
+from collections import namedtuple
+import numpy as np
+from onnx.backend.base import BackendRep
+import mxnet as mx
+
+# Using these functions for onnx test infrastructure.
+# Implemented by following onnx docs guide:
+# https://github.com/onnx/onnx/blob/master/docs/Implementing%20an%20ONNX%20backend.md
+# MXNetBackendRep object will be returned by MXNetBackend's prepare method which is used to
+# execute a model repeatedly.
+# Inputs will be passed to the run method of MXNetBackendRep class, it will perform computation and
+# retrieve the corresponding results for comparison to the onnx backend.
+# https://github.com/onnx/onnx/blob/master/onnx/backend/test/runner/__init__.py.
+
+class MXNetBackendRep(BackendRep):
+ """Running model inference on mxnet engine and return the result
+ to onnx test infrastructure for comparison."""
+ def __init__(self, symbol, params, device):
+ self.symbol = symbol
+ self.params = params
+ self.device = device
+
+ def run(self, inputs, **kwargs):
+ """Run model inference and return the result
+
+ Parameters
+ ----------
+ inputs : numpy array
+ input to run a layer on
+
+ Returns
+ -------
+ params : numpy array
+ result obtained after running the inference on mxnet
+ """
+ input_data = np.asarray(inputs[0], dtype='f')
+
+ # create module, passing cpu context
+ if self.device == 'CPU':
+ ctx = mx.cpu()
+ else:
+ raise NotImplementedError("Only CPU context is supported for now")
+
+ mod = mx.mod.Module(symbol=self.symbol, data_names=['input_0'], context=ctx,
+ label_names=None)
+ mod.bind(for_training=False, data_shapes=[('input_0', input_data.shape)],
+ label_shapes=None)
+ mod.set_params(arg_params=self.params, aux_params=None)
+
+ # run inference
+ batch = namedtuple('Batch', ['data'])
+
+ mod.forward(batch([mx.nd.array(input_data)]))
+ result = mod.get_outputs()[0].asnumpy()
+ return [result]
diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py
new file mode 100644
index 000000000000..155b835c28b9
--- /dev/null
+++ b/tests/python/unittest/onnx_backend_test.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.
+
+"""onnx test backend wrapper"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import unittest
+
+import onnx.backend.test
+#from onnx_test_utils
+import backend as mxnet_backend
+
+# This is a pytest magic variable to load extra plugins
+pytest_plugins = "onnx.backend.test.report",
+
+backend_test = onnx.backend.test.BackendTest(mxnet_backend, __name__)
+
+# Not implemented
+implemented_operators = [
+ 'test_abs*',
+ 'test_add*',
+ 'test_neg*',
+ 'test_relu*',
+ 'test_reshape_*',
+ 'test_sqrt*',
+ 'test_sub*',
+ 'test_sum*',
+ 'test_div*',
+ 'test_tanh*',
+ 'test_exp*',
+ 'test_floor*'
+ ]
+
+for op_test in implemented_operators:
+ backend_test.include(op_test)
+
+# import all test cases at global scope to make them visible to python.unittest
+globals().update(backend_test
+ .enable_report()
+ .test_cases)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/python/unittest/test_super_resolution.py b/tests/python/unittest/test_super_resolution.py
new file mode 100644
index 000000000000..0fdfa63a63d6
--- /dev/null
+++ b/tests/python/unittest/test_super_resolution.py
@@ -0,0 +1,61 @@
+# 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.
+
+"""Testing super_resolution model conversion"""
+from __future__ import absolute_import as _abs
+from __future__ import print_function
+from collections import namedtuple
+import mxnet as mx
+from mxnet.test_utils import download
+import mxnet.contrib.onnx._import as onnx_mxnet
+import numpy as np
+from PIL import Image
+
+model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx'
+
+download(model_url, 'super_resolution.onnx')
+
+print("Converting onnx format to mxnet's symbol and params...")
+sym, params = onnx_mxnet.import_model('super_resolution.onnx')
+
+# Load test image
+input_image_dim = 224
+output_image_dim = 672
+img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg'
+download(img_url, 'super_res_input.jpg')
+img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim))
+img_ycbcr = img.convert("YCbCr")
+img_y, img_cb, img_cr = img_ycbcr.split()
+x = np.array(img_y)[np.newaxis, np.newaxis, :, :]
+
+# create module
+mod = mx.mod.Module(symbol=sym, data_names=['input_0'], label_names=None)
+mod.bind(for_training=False, data_shapes=[('input_0', x.shape)])
+mod.set_params(arg_params=params, aux_params=None)
+
+# run inference
+Batch = namedtuple('Batch', ['data'])
+mod.forward(Batch([mx.nd.array(x)]))
+
+# Save the result
+img_out_y = Image.fromarray(np.uint8(mod.get_outputs()[0][0][0].asnumpy().clip(0, 255)), mode='L')
+
+result_img = Image.merge(
+ "YCbCr", [img_out_y,
+ img_cb.resize(img_out_y.size, Image.BICUBIC),
+ img_cr.resize(img_out_y.size, Image.BICUBIC)]).convert("RGB")
+result_img.save("super_res_output.jpg")