Skip to content

Commit e6f5d52

Browse files
Added Intel(R) initialization routine, adopted from Intel's patches to NumPy, that changes the interpretation of unset MKL_THREADING_LAYER to mean use Intel(R) OpenMP unless Gnu OMP was detected to be already loaded, in which case use Gnu OpenMP instead
1 parent 8ec516b commit e6f5d52

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

mkl/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,33 @@
2323
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2424
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525

26+
import sys
27+
28+
class RTLD_for_MKL():
29+
def __init__(self):
30+
self.saved_rtld = None
31+
32+
def __enter__(self):
33+
import ctypes
34+
try:
35+
self.saved_rtld = sys.getdlopenflags()
36+
# python loads libraries with RTLD_LOCAL, but MKL requires RTLD_GLOBAL
37+
# pre-load MKL with RTLD_GLOBAL before loading the native extension
38+
sys.setdlopenflags(self.saved_rtld | ctypes.RTLD_GLOBAL)
39+
except AttributeError:
40+
pass
41+
del ctypes
42+
43+
def __exit__(self, *args):
44+
if self.saved_rtld:
45+
sys.setdlopenflags(self.saved_rtld)
46+
self.saved_rtld = None
47+
48+
with RTLD_for_MKL():
49+
from . import _mklinit
50+
51+
del RTLD_for_MKL
52+
del sys
2653

2754
from ._py_mkl_service import *
2855

mkl/_mklinitmodule.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/* -*- c -*- */
2+
3+
/*
4+
* This is a dummy module whose purpose is to get distutils to generate the
5+
* configuration files before the libraries are made.
6+
*/
7+
8+
#if (defined(USING_MKL_RT) && defined(__linux__))
9+
#define FORCE_PRELOADING 1
10+
#define _GNU_SOURCE 1
11+
#include <dlfcn.h>
12+
#include <string.h>
13+
#undef _GNU_SOURCE
14+
#endif
15+
16+
#include <Python.h>
17+
#if PY_MAJOR_VERSION >= 3
18+
#define IS_PY3K
19+
#endif
20+
#include "mkl.h"
21+
22+
static struct PyMethodDef methods[] = {
23+
{NULL, NULL, 0, NULL}
24+
};
25+
26+
static inline void _set_mkl_ilp64() {
27+
#ifdef USING_MKL_RT
28+
int i = mkl_set_interface_layer(MKL_INTERFACE_ILP64);
29+
#endif
30+
return;
31+
}
32+
33+
static inline void _set_mkl_lp64() {
34+
#ifdef USING_MKL_RT
35+
int i = mkl_set_interface_layer(MKL_INTERFACE_LP64);
36+
#endif
37+
return;
38+
}
39+
40+
static void _preload_threading_layer() {
41+
#if FORCE_PRELOADING
42+
#define VERBOSE(...) if(verbose) printf("mkl-service + Intel(R) MKL: " __VA_ARGS__)
43+
#define SET_MTLAYER(L) do { \
44+
VERBOSE("setting Intel(R) MKL to use " #L " OpenMP runtime\n"); \
45+
mkl_set_threading_layer(MKL_THREADING_##L); \
46+
setenv("MKL_THREADING_LAYER", #L, 0); \
47+
} while(0)
48+
#define PRELOAD(lib) do { \
49+
VERBOSE("preloading %s runtime\n", lib); \
50+
dlopen(lib, RTLD_LAZY|RTLD_GLOBAL); \
51+
} while(0)
52+
/*
53+
* The following is the pseudo-code skeleton for reinterpreting unset MKL_THREADING_LAYER
54+
*
55+
* if MKL_THREADING_LAYER is empty
56+
* if kmp_calloc (or a suitable symbol identified by Terry) is loaded,
57+
* we are using Intel (R) OpenMP, i.e. reinterpret as implicit value of INTEL
58+
* otherwise check if other Open MP is loaded by checking get_omp_num_threads symbol
59+
* if not loaded:
60+
* assume INTEL, and force loading of IOMP5
61+
* if loaded:
62+
* if Gnu OMP, set MKL_THREADING_LAYER=GNU, and call set_mkl_threading_layer(MKL_THREADING_GNU)
63+
* if other vendors?
64+
* if MKL_THREADING_LAYER is INTEL
65+
* force loading of iomp, to preempt possibility of other modules loading other OMP library before MKL is actually used
66+
*
67+
* should we treat other possible values of MKL_THREADING_LAYER specially?
68+
*
69+
*/
70+
71+
const char *libiomp = "libiomp5.so";
72+
const char *verbose = getenv("MKL_VERBOSE");
73+
const char *mtlayer = getenv("MKL_THREADING_LAYER");
74+
void *omp = dlsym(RTLD_DEFAULT, "omp_get_num_threads");
75+
const char *omp_name = "(unidentified)";
76+
const char *iomp = NULL; /* non-zero indicates Intel OpenMP is loaded */
77+
Dl_info omp_info;
78+
79+
if(verbose && (verbose[0] == 0 || atoi(verbose) == 0))
80+
verbose = NULL;
81+
82+
VERBOSE("THREADING LAYER: %s\n", mtlayer);
83+
84+
if(omp) {
85+
if(dladdr(omp, &omp_info)) {
86+
omp_name = basename(omp_info.dli_fname); /* GNU version doesn't modify argument */
87+
iomp = strstr(omp_name, libiomp);
88+
}
89+
VERBOSE("%s OpenMP runtime %s is already loaded\n", iomp?"Intel(R)":"Other vendor", omp_name);
90+
}
91+
if(!mtlayer || mtlayer[0] == 0) { /* unset or empty */
92+
if(omp) { /* if OpenMP runtime is loaded */
93+
if(iomp) /* if Intel runtime is loaded */
94+
SET_MTLAYER(INTEL);
95+
else /* otherwise, assume it is GNU OpenMP */
96+
SET_MTLAYER(GNU);
97+
} else { /* nothing is loaded */
98+
SET_MTLAYER(INTEL);
99+
PRELOAD(libiomp);
100+
}
101+
} else if(strcasecmp(mtlayer, "intel") == 0) { /* Intel runtime is requested */
102+
if(omp && !iomp) {
103+
fprintf(stderr, "Error: mkl-service + Intel(R) MKL: MKL_THREADING_LAYER=INTEL is incompatible with %s library."
104+
"\n\tTry to import numpy first or set the threading layer accordingly. "
105+
"Set MKL_SERVICE_FORCE_INTEL to force it.\n", omp_name);
106+
if(!getenv("MKL_SERVICE_FORCE_INTEL"))
107+
exit(1);
108+
} else
109+
PRELOAD(libiomp);
110+
}
111+
#endif
112+
return;
113+
}
114+
115+
static inline void _set_mkl_interface() {
116+
_set_mkl_lp64();
117+
_preload_threading_layer();
118+
}
119+
120+
#if defined(IS_PY3K)
121+
static struct PyModuleDef moduledef = {
122+
PyModuleDef_HEAD_INIT,
123+
"mklinit",
124+
NULL,
125+
-1,
126+
methods,
127+
NULL,
128+
NULL,
129+
NULL,
130+
NULL
131+
};
132+
#endif
133+
134+
/* Initialization function for the module */
135+
#if defined(IS_PY3K)
136+
PyMODINIT_FUNC PyInit__mklinit(void) {
137+
PyObject *m;
138+
139+
_set_mkl_interface();
140+
m = PyModule_Create(&moduledef);
141+
if (!m) {
142+
return NULL;
143+
}
144+
145+
return m;
146+
}
147+
#else
148+
PyMODINIT_FUNC
149+
init_mklinit(void) {
150+
_set_mkl_interface();
151+
Py_InitModule("_mklinit", methods);
152+
}
153+
#endif

mkl/setup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def configuration(parent_package='', top_path=None):
3939
mkl_library_dirs = mkl_info.get('library_dirs', [])
4040
mkl_libraries = mkl_info.get('libraries', ['mkl_rt'])
4141

42+
defs = []
43+
if any(['mkl_rt' in li for li in mkl_libraries]):
44+
#libs += ['dl'] - by default on Linux
45+
defs += [('USING_MKL_RT', None)]
46+
4247
try:
4348
from Cython.Build import cythonize
4449
sources = [join(pdir, '_mkl_service.pyx')]
@@ -50,6 +55,19 @@ def configuration(parent_package='', top_path=None):
5055
raise ValueError(str(e) + '. ' +
5156
'Cython is required to build the initial .c file.')
5257

58+
config.add_extension(
59+
'_mklinit',
60+
sources=['_mklinitmodule.c'],
61+
define_macros=defs,
62+
include_dirs=[mkl_include_dirs],
63+
library_dirs=[mkl_library_dirs],
64+
libraries=mkl_libraries,
65+
extra_compile_args=[
66+
'-DNDEBUG'
67+
# '-g', '-O2', '-Wall',
68+
]
69+
)
70+
5371
config.add_extension(
5472
'_py_mkl_service',
5573
sources=sources,

0 commit comments

Comments
 (0)