Skip to content

Commit

Permalink
Fix PR apache#15489 (Dynamic Library Loading Support) (apache#15760)
Browse files Browse the repository at this point in the history
* Dynamic Library Loading Support (apache#15489)

* Accelerator APIs header file

* adding example to test accelerator loading

* adding c_api function header

* modifying header file path

* creating templates for call to lib fns

* modifying target to mxnet_static for libdl

* rebaseing with master

* returning nullptr handle if library not loaded

* refactoring code to load libraries dynamically

* addressing review comments

* using static cast

* pylint fix

* moving library.h file to src/common/

* adding dynamic loading support for windows

* updating header guard

* fixing headers

* fixing windows casting error

* declaring library functions for windows

* adding library testing module in examples

* adding unit test to test library loading

* correcting file names

* updating error messages

* getting error message from DL library

* adding unit test to gpu suite

* correcting windows pointer

* requiring absolute path to library

* changing file description

* addressing review comments - adding more docs, windows error msg

* addressing PR comments

* checking machine type for unit test

* “re-trigger”

* added map to store loaded libraries

* added dlclose calls in naive & threaded engines

* removed library map declaration in cc file

* added windows free

* fixed formatting

* added cast to HMODULE for void* for windows

* retrigger CI for flaky unix_cpu

* build library and stash it in CI

* modifying unittest to use CI built binary

* Retrigger CI

* adding dlclose to initialize destructor

* adding sample_lib target to all in Makefile
  • Loading branch information
mseth10 authored and Ubuntu committed Aug 20, 2019
1 parent 28bef15 commit 2691c96
Show file tree
Hide file tree
Showing 18 changed files with 555 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,8 @@ else()

endif()

add_library(sample_lib SHARED ${CMAKE_CURRENT_SOURCE_DIR}/example/lib_api/mylib.cc)
target_include_directories(sample_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/mxnet)
set(MXNET_INSTALL_TARGETS mxnet)
if(UNIX)
# Create dummy file since we want an empty shared library before linking
Expand All @@ -701,9 +703,13 @@ if(UNIX)
add_library(mxnet SHARED ${DUMMY_SOURCE})
target_link_libraries(mxnet PRIVATE ${BEGIN_WHOLE_ARCHIVE} $<TARGET_FILE:mxnet_static> ${END_WHOLE_ARCHIVE})
target_link_libraries(mxnet PRIVATE mxnet_static)
target_link_libraries(mxnet_static PUBLIC ${CMAKE_DL_LIBS})
target_compile_options(sample_lib PUBLIC -shared)
set_target_properties(mxnet_static PROPERTIES OUTPUT_NAME mxnet)
else()
add_library(mxnet SHARED ${SOURCE})
target_compile_options(sample_lib PUBLIC /LD)
set_target_properties(sample_lib PROPERTIES PREFIX "lib")
endif()

if(USE_CUDA)
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ else
CFLAGS += -O3 -DNDEBUG=1
endif
CFLAGS += -I$(TPARTYDIR)/mshadow/ -I$(TPARTYDIR)/dmlc-core/include -fPIC -I$(NNVM_PATH)/include -I$(DLPACK_PATH)/include -I$(TPARTYDIR)/tvm/include -Iinclude $(MSHADOW_CFLAGS)
LDFLAGS = -pthread $(MSHADOW_LDFLAGS) $(DMLC_LDFLAGS)
LDFLAGS = -pthread -ldl $(MSHADOW_LDFLAGS) $(DMLC_LDFLAGS)

ifeq ($(ENABLE_TESTCOVERAGE), 1)
CFLAGS += --coverage
Expand Down Expand Up @@ -454,7 +454,7 @@ endif
.PHONY: clean all extra-packages test lint docs clean_all rcpplint rcppexport roxygen\
cython2 cython3 cython cyclean

all: lib/libmxnet.a lib/libmxnet.so $(BIN) extra-packages
all: lib/libmxnet.a lib/libmxnet.so $(BIN) extra-packages sample_lib

SRC = $(wildcard src/*/*/*/*.cc src/*/*/*.cc src/*/*.cc src/*.cc)
OBJ = $(patsubst %.cc, build/%.o, $(SRC))
Expand Down Expand Up @@ -659,6 +659,9 @@ cpplint:
pylint:
python3 -m pylint --rcfile=$(ROOTDIR)/ci/other/pylintrc --ignore-patterns=".*\.so$$,.*\.dll$$,.*\.dylib$$" python/mxnet tools/caffe_converter/*.py

sample_lib:
$(CXX) -shared -fPIC example/lib_api/mylib.cc -o libsample_lib.so -I include/mxnet

doc: docs

docs:
Expand Down
10 changes: 5 additions & 5 deletions ci/jenkins/Jenkins_steps.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
utils = load('ci/Jenkinsfile_utils.groovy')

// mxnet libraries
mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a'
mx_lib_cython = 'lib/libmxnet.so, lib/libmxnet.a, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a, python/mxnet/_cy2/*.so, python/mxnet/_cy3/*.so'
mx_lib = 'lib/libmxnet.so, lib/libmxnet.a, libsample_lib.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a'
mx_lib_cython = 'lib/libmxnet.so, lib/libmxnet.a, libsample_lib.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a, python/mxnet/_cy2/*.so, python/mxnet/_cy3/*.so'

// Python wheels
mx_pip = 'build/*.whl'
Expand All @@ -33,11 +33,11 @@ mx_pip = 'build/*.whl'
mx_cmake_lib = 'build/libmxnet.so, build/libmxnet.a, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so'
mx_cmake_lib_cython = 'build/libmxnet.so, build/libmxnet.a, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so, python/mxnet/_cy2/*.so, python/mxnet/_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/libmxnet.a, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests'
mx_cmake_lib_debug = 'build/libmxnet.so, build/libmxnet.a, build/libsample_lib.so, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests'
mx_cmake_mkldnn_lib = 'build/libmxnet.so, build/libmxnet.a, build/3rdparty/dmlc-core/libdmlc.a, build/tests/mxnet_unit_tests, build/3rdparty/openmp/runtime/src/libomp.so, build/3rdparty/mkldnn/src/libmkldnn.so.0'
mx_mkldnn_lib = 'lib/libmxnet.so, lib/libmxnet.a, lib/libiomp5.so, lib/libmkldnn.so.0, lib/libmklml_intel.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a'
mx_mkldnn_lib = 'lib/libmxnet.so, lib/libmxnet.a, libsample_lib.so, lib/libiomp5.so, lib/libmkldnn.so.0, lib/libmklml_intel.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a'
mx_tensorrt_lib = 'build/libmxnet.so, lib/libnvonnxparser_runtime.so.0, lib/libnvonnxparser.so.0, lib/libonnx_proto.so, lib/libonnx.so'
mx_lib_cpp_examples = 'lib/libmxnet.so, lib/libmxnet.a, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a, 3rdparty/ps-lite/build/libps.a, deps/lib/libprotobuf-lite.a, deps/lib/libzmq.a, build/cpp-package/example/*, python/mxnet/_cy2/*.so, python/mxnet/_cy3/*.so'
mx_lib_cpp_examples = 'lib/libmxnet.so, lib/libmxnet.a, libsample_lib.so, 3rdparty/dmlc-core/libdmlc.a, 3rdparty/tvm/nnvm/lib/libnnvm.a, 3rdparty/ps-lite/build/libps.a, deps/lib/libprotobuf-lite.a, deps/lib/libzmq.a, build/cpp-package/example/*, python/mxnet/_cy2/*.so, python/mxnet/_cy3/*.so'
mx_lib_cpp_examples_cpu = 'build/libmxnet.so, build/cpp-package/example/*'

// Python unittest for CPU
Expand Down
31 changes: 31 additions & 0 deletions example/lib_api/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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.

all:
g++ -shared -fPIC mylib.cc -o mylib.so -I ../../include/mxnet

test:
g++ -std=c++11 -O3 -o libtest libtest.cc -ldl -I ../../include/mxnet

windows:
cl /LD mylib.cc

win_test:
cl libtest.cc

clean:
rm -rf mylib.so libtest
78 changes: 78 additions & 0 deletions example/lib_api/libtest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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) 2015 by Contributors
* \file libtest.cc
* \brief This test checks if the library is implemented correctly
* and does not involve dynamic loading of library into MXNet
* This test is supposed to be run before test.py
*/

#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#include <iostream>
#include "lib_api.h"

#define MXNET_VERSION 10500

int main(void) {
// Get a handle to the library.
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
HINSTANCE handle;
handle = LoadLibrary(TEXT("mylib.dll"));
#else
void *handle;
handle = dlopen("mylib.so", RTLD_LAZY);
#endif

if (!handle) {
std::cerr << "Unable to load library" << std::endl;
return 1;
}

// get initialize function address from the library
initialize_t init_lib;
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
init_lib = (initialize_t) GetProcAddress(handle, MXLIB_INITIALIZE_STR);
#else
init_lib = (initialize_t) dlsym(handle, MXLIB_INITIALIZE_STR);
#endif

if (!init_lib) {
std::cerr << "Unable to get function 'intialize' from library" << std::endl;
return 1;
}

// Call the function.
(init_lib)(MXNET_VERSION);

// Deallocate memory.
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
FreeLibrary(handle);
#else
dlclose(handle);
#endif

return 0;
}
37 changes: 37 additions & 0 deletions example/lib_api/mylib.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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) 2015 by Contributors
* \file mylib.cc
* \brief Sample library file
*/

#include <iostream>
#include "lib_api.h"

int initialize(int version) {
if (version >= 10400) {
std::cout << "MXNet version " << version << " supported" << std::endl;
return 1;
} else {
std::cout << "MXNet version " << version << " not supported" << std::endl;
return 0;
}
}
31 changes: 31 additions & 0 deletions example/lib_api/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/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

import mxnet as mx
import os

if (os.name=='posix'):
mx.library.load('mylib.so')
elif (os.name=='nt'):
mx.library.load('mylib.dll')
7 changes: 7 additions & 0 deletions include/mxnet/c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ MXNET_DLL const char *MXGetLastError();
// Part 0: Global State setups
//-------------------------------------

/*!
* \brief Load library dynamically
* \param path to the library .so file
* \return 0 when success, -1 when failure happens.
*/
MXNET_DLL int MXLoadLib(const char *path);

/*!
* \brief Get list of features supported on the runtime
* \param libFeature pointer to array of LibFeature
Expand Down
50 changes: 50 additions & 0 deletions include/mxnet/lib_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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) 2015 by Contributors
* \file lib_api.h
* \brief APIs to interact with libraries
*/
#ifndef MXNET_LIB_API_H_
#define MXNET_LIB_API_H_

/*!
* \brief Following are the APIs implemented in the external library
* Each API has a #define string that is used to lookup the function in the library
* Followed by the function declaration
*/
#define MXLIB_INITIALIZE_STR "initialize"
typedef int (*initialize_t)(int);

extern "C" {
/*!
* \brief Checks if the MXNet version is supported by the library.
* If supported, initializes the library.
* \param version MXNet version number passed to library and defined as:
* MXNET_VERSION = (MXNET_MAJOR*10000 + MXNET_MINOR*100 + MXNET_PATCH)
* \return Non-zero value on error i.e. library incompatible with passed MXNet version
*/
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
__declspec(dllexport) int __cdecl initialize(int);
#else
int initialize(int);
#endif
}
#endif // MXNET_LIB_API_H_
1 change: 1 addition & 0 deletions python/mxnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .base import MXNetError
from .util import is_np_shape, set_np_shape, np_shape, use_np_shape
from . import base
from . import library
from . import contrib
from . import ndarray
from . import ndarray as nd
Expand Down
2 changes: 1 addition & 1 deletion python/mxnet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __repr__(self):


class MXNetError(Exception):
"""Error that will be throwed by all mxnet functions."""
"""Error that will be thrown by all mxnet functions."""
pass


Expand Down
49 changes: 49 additions & 0 deletions python/mxnet/library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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
"""Library management API of mxnet."""
from __future__ import absolute_import
import ctypes
import os
from .base import _LIB, check_call, MXNetError

def load(path):
"""Loads library dynamically.
Parameters
---------
path : Path to library .so/.dll file
Returns
---------
void
"""
#check if path exists
if not os.path.exists(path):
raise MXNetError("load path %s does NOT exist" % path)
#check if path is an absolute path
if not os.path.isabs(path):
raise MXNetError("load path %s is not an absolute path" % path)
#check if path is to a library file
_, file_ext = os.path.splitext(path)
if not file_ext in ['.so', '.dll']:
raise MXNetError("load path %s is NOT a library file" % path)

byt_obj = path.encode('utf-8')
chararr = ctypes.c_char_p(byt_obj)
check_call(_LIB.MXLoadLib(chararr))
Loading

0 comments on commit 2691c96

Please sign in to comment.