You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by sa...@apache.org on 2020/11/14 07:14:45 UTC

[incubator-mxnet] branch master updated: External Operators 2 (#19431)

This is an automated email from the ASF dual-hosted git repository.

samskalicky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-mxnet.git


The following commit(s) were added to refs/heads/master by this push:
     new 6bc0647  External Operators 2 (#19431)
6bc0647 is described below

commit 6bc064771d805163cd22123e9b3ec25bb55341d3
Author: Sam Skalicky <sa...@gmail.com>
AuthorDate: Fri Nov 13 23:13:06 2020 -0800

    External Operators 2 (#19431)
    
    * initial commit
    
    * license fix
    
    * changed path var, formatting
    
    * add test to linux stages in ci
    
    * disable test on osx stage in ci
    
    * cleaned up example CMakeLists.txt removed -shared from GPU
    
    * moved windows check
    
    Co-authored-by: Ubuntu <ub...@ip-172-31-6-220.us-west-2.compute.internal>
    Co-authored-by: Manu Seth <se...@amazon.com>
---
 .github/workflows/os_x_staticbuild.yml             |  6 +-
 CMakeLists.txt                                     |  9 +++
 ci/docker/runtime_functions.sh                     | 10 ++++
 ci/jenkins/Jenkins_steps.groovy                    | 10 ++--
 example/extensions/lib_external_ops/CMakeLists.txt | 18 ++++++
 example/extensions/lib_external_ops/README.md      | 70 ++++++++++++++++++++++
 example/extensions/lib_external_ops/init_lib.cc    | 39 ++++++++++++
 example/extensions/lib_external_ops/min_ex-inl.h   | 66 ++++++++++++++++++++
 example/extensions/lib_external_ops/min_ex.cc      | 40 +++++++++++++
 example/extensions/lib_external_ops/min_ex.cu      | 35 +++++++++++
 .../extensions/lib_external_ops/test_loading.py    | 42 +++++++++++++
 tests/python/gpu/test_extensions_gpu.py            | 25 +++++++-
 tests/python/unittest/test_extensions.py           | 25 +++++++-
 13 files changed, 383 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/os_x_staticbuild.yml b/.github/workflows/os_x_staticbuild.yml
index a93e580..780e255 100644
--- a/.github/workflows/os_x_staticbuild.yml
+++ b/.github/workflows/os_x_staticbuild.yml
@@ -44,6 +44,6 @@ jobs:
 
       - name: Test project
         run: |
-          python3 -m pytest -n 4 --durations=50 --verbose tests/python/unittest/ -k 'not test_operator and not (test_subgraph or test_custom_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'not serial'
-          MXNET_ENGINE_TYPE=NaiveEngine python3 -m pytest -n 4 --durations=50 --verbose tests/python/unittest/ -k 'test_operator and not (test_subgraph or test_custom_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'not serial'
-          python3 -m pytest --durations=50 --verbose tests/python/unittest/ -k 'not (test_subgraph or test_custom_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'serial'
+          python3 -m pytest -n 4 --durations=50 --verbose tests/python/unittest/ -k 'not test_operator and not (test_subgraph or test_custom_op or test_external_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'not serial'
+          MXNET_ENGINE_TYPE=NaiveEngine python3 -m pytest -n 4 --durations=50 --verbose tests/python/unittest/ -k 'test_operator and not (test_subgraph or test_custom_op or test_external_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'not serial'
+          python3 -m pytest --durations=50 --verbose tests/python/unittest/ -k 'not (test_subgraph or test_custom_op or test_external_op or test_recordimage_dataset_with_data_loader_multiworker or test_multi_worker or test_multi_worker_shape or test_multi_worker_forked_data_loader or test_multi_worker_dataloader_release_pool)' -m 'serial'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 07075d7..30839b4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -89,6 +89,7 @@ option(USE_TENSORRT "Enable inference optimization with TensorRT." OFF)
 option(USE_ASAN "Enable Clang/GCC ASAN sanitizers." OFF)
 cmake_dependent_option(ENABLE_TESTCOVERAGE "Enable compilation with test coverage metric output" OFF "NOT MSVC" OFF)
 option(USE_INT64_TENSOR_SIZE "Use int64_t to represent the total number of elements in a tensor" OFF)
+option(BUILD_EXTENSION_PATH "Path to extension to build" "")
 option(BUILD_CYTHON_MODULES "Build cython modules." OFF)
 option(LOG_FATAL_THROW "Log exceptions but do not abort" ON)
 cmake_dependent_option(USE_SPLIT_ARCH_DLL "Build a separate DLL for each Cuda arch (Windows only)." ON "MSVC" OFF)
@@ -787,6 +788,14 @@ add_library(transposerowsp_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/example/extens
 add_library(subgraph_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/example/extensions/lib_subgraph/subgraph_lib.cc ${CMAKE_CURRENT_SOURCE_DIR}/src/lib_api.cc)
 add_library(pass_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/example/extensions/lib_pass/pass_lib.cc ${CMAKE_CURRENT_SOURCE_DIR}/src/lib_api.cc)
 
+if(IS_DIRECTORY ${BUILD_EXTENSION_PATH})
+  if(MSVC)
+    message(FATAL_ERROR "Windows builds are not support for external ops")
+  else()
+    add_subdirectory(${BUILD_EXTENSION_PATH} ${BUILD_EXTENSION_PATH}/build)
+  endif()
+endif()
+
 target_include_directories(customop_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/mxnet)
 target_include_directories(transposecsr_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/mxnet)
 target_include_directories(transposerowsp_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/mxnet)
diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh
index d436a3e..f5ee9ba 100755
--- a/ci/docker/runtime_functions.sh
+++ b/ci/docker/runtime_functions.sh
@@ -267,6 +267,7 @@ build_centos7_cpu() {
         -DUSE_MKLDNN=OFF \
         -DUSE_DIST_KVSTORE=ON \
         -DUSE_CUDA=OFF \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -298,6 +299,7 @@ build_centos7_gpu() {
         -DUSE_CUDA=ON \
         -DMXNET_CUDA_ARCH="$CI_CMAKE_CUDA_ARCH" \
         -DUSE_DIST_KVSTORE=ON\
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -319,6 +321,7 @@ build_ubuntu_cpu_openblas() {
         -DUSE_CUDA=OFF \
         -DUSE_DIST_KVSTORE=ON \
         -DBUILD_CYTHON_MODULES=ON \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -334,6 +337,7 @@ build_ubuntu_cpu_mkl() {
         -DUSE_TVM_OP=ON \
         -DUSE_MKL_IF_AVAILABLE=ON \
         -DUSE_BLAS=MKL \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -GNinja /work/mxnet
     ninja
 }
@@ -366,6 +370,7 @@ build_ubuntu_cpu_cmake_no_tvm_op() {
         -DUSE_OPENCV=ON \
         -DUSE_SIGNAL_HANDLER=ON \
         -DCMAKE_BUILD_TYPE=Release \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja \
         /work/mxnet
 
@@ -518,6 +523,7 @@ build_ubuntu_cpu_mkldnn() {
         -DUSE_MKLDNN=ON \
         -DUSE_CUDA=OFF \
         -DUSE_CPP_PACKAGE=ON \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -533,6 +539,7 @@ build_ubuntu_cpu_mkldnn_mkl() {
         -DUSE_TVM_OP=ON \
         -DUSE_MKL_IF_AVAILABLE=ON \
         -DUSE_BLAS=MKL \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -GNinja /work/mxnet
     ninja
 }
@@ -604,6 +611,7 @@ build_ubuntu_gpu_mkldnn() {
         -DUSE_CUDA=ON \
         -DMXNET_CUDA_ARCH="$CI_CMAKE_CUDA_ARCH" \
         -DUSE_CPP_PACKAGE=ON \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -618,6 +626,7 @@ build_ubuntu_gpu_mkldnn_nocudnn() {
         -DMXNET_CUDA_ARCH="$CI_CMAKE_CUDA_ARCH" \
         -DUSE_CUDNN=OFF \
         -DUSE_CPP_PACKAGE=ON \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
@@ -635,6 +644,7 @@ build_ubuntu_gpu_cuda101_cudnn7() {
         -DUSE_CPP_PACKAGE=ON \
         -DUSE_DIST_KVSTORE=ON \
         -DBUILD_CYTHON_MODULES=ON \
+        -DBUILD_EXTENSION_PATH=/work/mxnet/example/extensions/lib_external_ops \
         -G Ninja /work/mxnet
     ninja
 }
diff --git a/ci/jenkins/Jenkins_steps.groovy b/ci/jenkins/Jenkins_steps.groovy
index d7623f9..fca8e44 100644
--- a/ci/jenkins/Jenkins_steps.groovy
+++ b/ci/jenkins/Jenkins_steps.groovy
@@ -23,18 +23,18 @@
 utils = load('ci/Jenkinsfile_utils.groovy')
 
 // mxnet libraries
-mx_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, build/3rdparty/openmp/runtime/src/libomp.so'
-mx_lib_cython = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, python/mxnet/_cy3/*.so, build/3rdparty/openmp/runtime/src/libomp.so, python/mxnet/_ffi/_cy3/*.so'
+mx_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, example/extensions/lib_external_ops/build/libexternal_lib.so, build/3rdparty/openmp/runtime/src/libomp.so'
+mx_lib_cython = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, example/extensions/lib_external_ops/build/libexternal_lib.so, python/mxnet/_cy3/*.so, build/3rdparty/openmp/runtime/src/libomp.so, python/mxnet/_ffi/_cy3/*.so'
 
 // mxnet cmake libraries, in cmake builds we do not produce a libnvvm static library by default.
 mx_cmake_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so'
-mx_cmake_lib_no_tvm_op = 'build/libmxnet.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so'
+mx_cmake_lib_no_tvm_op = 'build/libmxnet.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, example/extensions/lib_external_ops/build/libexternal_lib.so, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so'
 mx_cmake_lib_cython = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so, python/mxnet/_cy3/*.so, python/mxnet/_ffi/_cy3/*.so'
 // mxnet cmake libraries, in cmake builds we do not produce a libnvvm static library by default.
 mx_cmake_lib_debug = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, build/tests/mxnet_unit_tests'
-mx_mkldnn_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so'
+mx_mkldnn_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, example/extensions/lib_external_ops/build/libexternal_lib.so'
 mx_tensorrt_lib = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so, lib/libnvonnxparser_runtime.so.0, lib/libnvonnxparser.so.0, lib/libonnx_proto.so, lib/libonnx.so'
-mx_lib_cpp_examples = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, python/mxnet/_cy3/*.so, python/mxnet/_ffi/_cy3/*.so'
+mx_lib_cpp_examples = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, example/extensions/lib_external_ops/build/libexternal_lib.so, python/mxnet/_cy3/*.so, python/mxnet/_ffi/_cy3/*.so'
 mx_lib_cpp_examples_no_tvm_op = 'build/libmxnet.so, build/libcustomop_lib.so, build/libcustomop_gpu_lib.so, build/libsubgraph_lib.so, build/3rdparty/openmp/runtime/src/libomp.so, python/mxnet/_cy3/*.so, python/mxnet/_ffi/_cy3/*.so'
 mx_lib_cpp_examples_cpu = 'build/libmxnet.so, build/3rdparty/tvm/libtvm_runtime.so, build/libtvmop.so, build/tvmop.conf, build/3rdparty/openmp/runtime/src/libomp.so'
 mx_cd_lib = 'lib/libmxnet.so, licenses/*, lib/libgfortran.so.*, lib/libopenblas.so.0, include/mkldnn/dnnl_version.h, include/mkldnn/dnnl_config.h'
diff --git a/example/extensions/lib_external_ops/CMakeLists.txt b/example/extensions/lib_external_ops/CMakeLists.txt
new file mode 100644
index 0000000..383e529
--- /dev/null
+++ b/example/extensions/lib_external_ops/CMakeLists.txt
@@ -0,0 +1,18 @@
+# specify CXX sources
+FILE(GLOB CXX_SRCS
+  # Required files
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/lib_api.cc
+  # Your custom files
+  ${CMAKE_CURRENT_SOURCE_DIR}/init_lib.cc
+  ${CMAKE_CURRENT_SOURCE_DIR}/min_ex.cc
+  )
+
+# create library & set libraries
+add_library(external_lib SHARED ${CXX_SRCS})
+target_link_libraries(external_lib PUBLIC mxnet)
+
+if(USE_CUDA)
+  # specify GPU sources (optional)
+  FILE(GLOB CU_SRCS "*.cu")
+  target_sources(external_lib PUBLIC ${CU_SRCS})
+endif(USE_CUDA)
diff --git a/example/extensions/lib_external_ops/README.md b/example/extensions/lib_external_ops/README.md
new file mode 100644
index 0000000..40291e1
--- /dev/null
+++ b/example/extensions/lib_external_ops/README.md
@@ -0,0 +1,70 @@
+<!--- 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. -->
+
+External Operators Example and Tutorial
+=======================================
+
+## Introduction
+
+Extending MXNet with custom components used to mean distributing a custom fork. This feature allows adding custom components to MXNet by dynamically loading external libraries at runtime. Currently it is only supported on Linux systems (Windows and Mac are __NOT__ supported). 
+
+## Getting Started
+
+### Have MXNet Ready
+
+For this tutorial, clone MXNet from source like:
+```
+git clone https://github.com/apache/incubator-mxnet.git --recursive --init
+```
+
+Build MXNet like:
+```
+cp config/linux.cmake config.cmake
+mkdir build
+cd build
+cmake ..
+cmake --build .
+```
+
+## Run An Example
+
+This example shows compiling a custom backend operator and then dynamically loading it into MXNet at runtime. Go to the **lib_external_ops** directory and follow these steps:
+
+1. Touch or modify the **min_ex.cc** and/or **min_ex-inl.h** file(s)
+2. Go into the **build** directory that was created when building MXNet.
+3. Run `cmake .. -DBUILD_EXTENSION_PATH=$(pwd)/../example/extensions/lib_external_ops`
+4. Run `cmake --build .`
+5. Go to the **example/extensions/lib_external_ops** directory again
+6. Run `python test_loading.py` to execute the test program. You should see the following output:
+```
+Operator not registered yet
+MXNet version 20000 supported
+[]
+Operator executed successfully
+```
+
+## Writing an External Operator Library
+To build your own library containing custom components, compose a C++ source file like `mycomp_lib.cc`, include the `lib_api.h` header file, compile the `lib_api.cc` file, and implement the following required function:
+- `initialize` - Library Initialization Function
+
+Then create a CMakeLists.txt file and set `mxnet` as a link library like:
+```
+add_library(external_lib SHARED ${SRCS})
+target_link_libraries(external_lib PUBLIC mxnet)
+```
+
+Next, build MXNet and set the path to your directory with the CMakeLists.txt file via the `BUILD_EXTENSION_PATH` option. This will build your library with all of the MXNet includes. 
diff --git a/example/extensions/lib_external_ops/init_lib.cc b/example/extensions/lib_external_ops/init_lib.cc
new file mode 100644
index 0000000..efc5eb7
--- /dev/null
+++ b/example/extensions/lib_external_ops/init_lib.cc
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * Copyright (c) 2020 by Contributors
+ * \file init_lib.cc
+ * \brief initialize function implementation library file
+ */
+
+#include <iostream>
+#include "mxnet/lib_api.h"
+
+using namespace mxnet::ext;
+
+MXReturnValue initialize(int version) {
+  if (version >= 10700) {
+    std::cout << "MXNet version " << version << " supported" << std::endl;
+    return MX_SUCCESS;
+  } else {
+    MX_ERROR_MSG << "MXNet version " << version << " not supported";
+    return MX_FAIL;
+  }
+}
diff --git a/example/extensions/lib_external_ops/min_ex-inl.h b/example/extensions/lib_external_ops/min_ex-inl.h
new file mode 100644
index 0000000..79ce5d4
--- /dev/null
+++ b/example/extensions/lib_external_ops/min_ex-inl.h
@@ -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.
+ */
+
+/*!
+ * Copyright (c) 2020 by Contributors
+ * \file min_ex-inl.h
+ * \brief example external operator header file
+ */
+
+#ifndef MXNET_OPERATOR_TENSOR_MIN_EX_OP_INL_H_
+#define MXNET_OPERATOR_TENSOR_MIN_EX_OP_INL_H_
+
+#include <dmlc/parameter.h>
+#include <vector>
+#include <algorithm>
+#include "operator/mxnet_op.h"
+#include "operator/operator_common.h"
+#include "operator/elemwise_op_common.h"
+
+namespace mxnet {
+namespace op {
+
+template<typename xpu>
+void MinExForward(const nnvm::NodeAttrs& attrs,
+                  const OpContext& ctx,
+                  const std::vector<TBlob>& inputs,
+                  const std::vector<OpReqType>& req,
+                  const std::vector<TBlob>& outputs) {
+  //do nothing                                                                                                                                                                         
+}
+
+
+inline bool MinExOpShape(const nnvm::NodeAttrs& attrs,
+                         mxnet::ShapeVector* in_attrs,
+                         mxnet::ShapeVector* out_attrs) {
+    //do nothing                                                                                                                                                                       
+    return true;
+}
+
+inline bool MinExOpType(const nnvm::NodeAttrs& attrs,
+                        std::vector<int> *in_attrs,
+                        std::vector<int> *out_attrs) {
+  //do nothing                                                                                                                                                                         
+  return true;
+}
+
+}  // namespace op                                                                                                                                                                     
+}  // namespace mxnet                                                                                                                                                                  
+
+#endif  // MXNET_OPERATOR_TENSOR_MIN_EX_OP_INL_H_
diff --git a/example/extensions/lib_external_ops/min_ex.cc b/example/extensions/lib_external_ops/min_ex.cc
new file mode 100644
index 0000000..cb9f6dd
--- /dev/null
+++ b/example/extensions/lib_external_ops/min_ex.cc
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * Copyright (c) 2020 by Contributors
+ * \file min_ex.cc
+ * \brief example external operator source file
+ */
+
+#include "min_ex-inl.h"
+
+namespace mxnet {
+namespace op {
+
+NNVM_REGISTER_OP(min_ex)
+.describe("some description")
+.set_num_inputs(0)
+.set_num_outputs(0)
+.set_attr<mxnet::FInferShape>("FInferShape", MinExOpShape)
+.set_attr<nnvm::FInferType>("FInferType", MinExOpType)
+.set_attr<FCompute>("FCompute<cpu>", MinExForward<cpu>);
+
+}  // namespace op                                                                                                                                                                     
+}  // namespace mxnet 
diff --git a/example/extensions/lib_external_ops/min_ex.cu b/example/extensions/lib_external_ops/min_ex.cu
new file mode 100644
index 0000000..6257ea7
--- /dev/null
+++ b/example/extensions/lib_external_ops/min_ex.cu
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * Copyright (c) 2020 by Contributors
+ * \file min_ex.cu
+ * \brief example external operator CUDA source file
+ */
+
+#include "./min_ex-inl.h"
+
+namespace mxnet {
+namespace op {
+
+NNVM_REGISTER_OP(min_ex)
+.set_attr<FCompute>("FCompute<gpu>", MinExForward<gpu>);
+
+}  // namespace op
+}  // namespace mxnet
diff --git a/example/extensions/lib_external_ops/test_loading.py b/example/extensions/lib_external_ops/test_loading.py
new file mode 100644
index 0000000..1b4357c
--- /dev/null
+++ b/example/extensions/lib_external_ops/test_loading.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+# 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=arguments-differ
+
+# This test checks if dynamic loading of library into MXNet is successful
+# and checks the computation of an external operator
+
+import mxnet as mx
+import os
+
+# check if operator exists
+if hasattr(mx.nd, 'min_ex'):
+    raise Exception('Operator already loaded')
+else:
+    print('Operator not registered yet')
+
+# test loading library
+if (os.name == 'posix'):
+    path = os.path.abspath('build/libexternal_lib.so')
+    mx.library.load(path, False)
+
+# execute operator
+print(mx.nd.min_ex())
+print('Operator executed successfully')
diff --git a/tests/python/gpu/test_extensions_gpu.py b/tests/python/gpu/test_extensions_gpu.py
index 1cc06cd..9d36831 100644
--- a/tests/python/gpu/test_extensions_gpu.py
+++ b/tests/python/gpu/test_extensions_gpu.py
@@ -28,8 +28,8 @@ from mxnet.test_utils import download, is_cd_run, assert_almost_equal, default_c
 import pytest
 
 base_path = os.path.join(os.path.dirname(__file__), "../../..")
-def check_platform():
-    return platform.machine() not in ['x86_64', 'AMD64']
+def check_platform(supported_platforms=['x86_64', 'AMD64']):
+    return platform.machine() not in supported_platforms
 
 @pytest.mark.skipif(check_platform(), reason="not all machine types supported")
 @pytest.mark.skipif(is_cd_run(), reason="continuous delivery run - ignoring test")
@@ -89,3 +89,24 @@ def test_custom_op_gpu():
     mx.random.seed(128, ctx=mx.gpu())
     r4 = mx.nd.my_noisy_relu(d2)
     assert_almost_equal(r3.asnumpy(), r4.asnumpy(), rtol=1e-3, atol=1e-3)
+
+@pytest.mark.skipif(check_platform(['x86_64']), reason="not all machine types supported")
+@pytest.mark.skipif(is_cd_run(), reason="continuous delivery run - ignoring test")
+def test_external_op():
+    # check if operator already exists
+    if hasattr(mx.nd, 'min_ex'):
+        raise MXNetError('Operator already loaded')
+
+    lib = 'libexternal_lib.so'
+    fname = os.path.join(base_path,'example/extensions/lib_external_ops/build/'+lib)
+    if not os.path.exists(fname):
+        raise MXNetError("library %s not found " % lib)
+
+    fname = os.path.abspath(fname)
+    mx.library.load(fname, False)
+
+    # execute operator
+    try:
+        mx.nd.min_ex()
+    except:
+        raise MXNetError('Operator not loaded successfully')
diff --git a/tests/python/unittest/test_extensions.py b/tests/python/unittest/test_extensions.py
index 8d94680..9785901 100644
--- a/tests/python/unittest/test_extensions.py
+++ b/tests/python/unittest/test_extensions.py
@@ -28,8 +28,8 @@ from mxnet.test_utils import download, is_cd_run, assert_almost_equal, default_c
 import pytest
 
 base_path = os.path.join(os.path.dirname(__file__), "../../..")
-def check_platform():
-    return platform.machine() not in ['x86_64', 'AMD64']
+def check_platform(supported_platforms=['x86_64', 'AMD64']):
+    return platform.machine() not in supported_platforms
 
 @pytest.mark.skipif(check_platform(), reason="not all machine types supported")
 @pytest.mark.skipif(is_cd_run(), reason="continuous delivery run - ignoring test")
@@ -186,3 +186,24 @@ def test_subgraph():
     out5 = sym_block4(a_data, b_data)
     # check that result matches one executed by MXNet
     assert_almost_equal(out[0].asnumpy(), out5[0].asnumpy(), rtol=1e-3, atol=1e-3)
+
+@pytest.mark.skipif(check_platform(['x86_64']), reason="not all machine types supported")
+@pytest.mark.skipif(is_cd_run(), reason="continuous delivery run - ignoring test")
+def test_external_op():
+    # check if operator already exists
+    if hasattr(mx.nd, 'min_ex'):
+        raise MXNetError('Operator already loaded')
+
+    lib = 'libexternal_lib.so'
+    fname = os.path.join(base_path,'example/extensions/lib_external_ops/build/'+lib)
+    if not os.path.exists(fname):
+        raise MXNetError("library %s not found " % lib)
+
+    fname = os.path.abspath(fname)
+    mx.library.load(fname, False)
+
+    # execute operator
+    try:
+        mx.nd.min_ex()
+    except:
+        raise MXNetError('Operator not loaded successfully')