Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Fix PR #15489 (Dynamic Library Loading Support) #15760

Merged
merged 6 commits into from
Aug 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
mseth10 marked this conversation as resolved.
Show resolved Hide resolved

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
Copy link
Contributor

@larroy larroy Aug 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CI doesn't pass, I suggest to use $(CC) and $(CXX) and parameterize the linker so it's coming from environment as usual instead of hardcoded, if not it's not worth another CI round.

Copy link
Contributor

@samskalicky samskalicky Aug 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larroy this isnt building in the CI. this is just an example. See changes in the other Makefile or CMakeLists.txt for changes to build the library in the CI

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but still this is a PITA if a Makefile doesn't respect the compiler from the environment. That's why I said is not worth another CI round. If you make other changes I would change it. Or is there a reason not to?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does that work for windows? will it automatically have CXX be "cl" ?


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