Связать обернутые Cython функции C с BLAS из NumPy - PullRequest
0 голосов
/ 20 октября 2018

Я хочу использовать внутри расширения Cython некоторые функции C, определенные в файлах .c, которые используют подпрограммы BLAS, например,

cfile.c

double ddot(int *N, double *DX, int *INCX, double *DY, int *INCY);

double call_ddot(double* a, double* b, int n){
    int one = 1;
    return ddot(&n, a, &one, b, &one);
}

(скажем, функции делают большечем просто вызвать одну подпрограмму BLAS)

pyfile.pyx

cimport numpy as np
import numpy as np

cdef extern from "cfile.c":
    double call_ddot(double* a, double* b, int n)

def pyfun(np.ndarray[double, ndim=1] a):
    return call_ddot(&a[0], &a[0], <int> a.shape[0])

setup.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext
import numpy

setup(
    name  = "wrapped_cfun",
    packages = ["wrapped_cfun"],
    cmdclass = {'build_ext': build_ext},
    ext_modules = [Extension("wrapped_cfun.cython_part", sources=["pyfile.pyx"], include_dirs=[numpy.get_include()])]
)

Я хочу, чтобы этот пакет связывался с той же библиотекой BLAS, котораяустановленный NumPy или SciPy используют и хотели бы, чтобы его можно было устанавливать из PIP в других операционных системах, используя в качестве зависимостей numpy или scipy, без каких-либо дополнительных зависимостей, связанных с BLAS.

Есть ли какой-нибудь хак для setup.pyчто позволило бы мне сделать это так, чтобы он мог работать с любой реализацией BLAS?

Обновление: С помощью MKL я могу заставить его работать, изменив объект Extension наукажите на libmkl_rt, который можно извлечь из numpy, если установлен MKL, например: Extension("wrapped_cfun.cython_part", sources=["pyfile.pyx"], include_dirs=[numpy.get_include()], extra_link_args=["-L{path to python's lib dir}", "-l:libmkl_rt.{so, dll, dylib}"]) Однако этот же прием не работает для OpenBLAS (например, -l:libopenblasp-r0.2.20.so).Указание на libblas.{so,dll,dylib} не будет работать, если этот файл является ссылкой на libopenblas, но работает нормально, если это ссылка на libmkl_rt.

Обновление 2: Кажется, OpenBLAS называет свои функции C с помощьюзнак подчеркивания в конце, например, не ddot, а ddot_.Приведенный выше код с l:libopenblas будет работать, если я изменю ddot на ddot_ в файле .c.Мне все еще интересно, есть ли какой-нибудь (в идеале во время выполнения) механизм для определения того, какое имя следует использовать в файле c.

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

Альтернативой зависимости от компоновщика / загрузчика для обеспечения правильной функциональности blas является эмуляция разрешения необходимых символов blas (например, ddot) и использование обернутой * blas-функции, предоставляемой scipy. во время выполнения.

Не уверен, что этот подход превосходит «нормальный способ» построения, но хотел бы обратить на него ваше внимание, хотя бы потому, что мне этот подход интересен.

Идея в двух словах:

  1. Определить явную функцию-указатель на ddot -функцию, называемую my_ddot во фрагменте ниже.
  2. Use my_ddot -пойнт, где вы должны использовать ddot -потому.
  3. Инициализировать my_ddot -поинтер, когда модуль cython загружен с функциями, предоставляемыми scipy.

Здесьрабочий прототип (я использую C-code-verbatim, чтобы сделать фрагмент кода автономным и легко тестируемым в блокноте Юпитер, доверяю вам преобразовать его в нужный вам формат):

%%cython
# h-file:
cdef extern from *:
    """
    // blas-functionality,
    // will be initialized by cython when module is loaded:
    typedef double (*ddot_t)(int *N, double *DX, int *INCX, double *DY, int *INCY);
    extern ddot_t my_ddot;

    double call_ddot(double* a, double* b, int n);
    """
    ctypedef double (*ddot_t)(int *N, double *DX, int *INCX, double *DY, int *INCY)
    ddot_t my_ddot
    double call_ddot(double* a, double* b, int n)    

# init the functions of the c-library
# with blas-function provided by scipy
from scipy.linalg.cython_blas cimport ddot
my_ddot=ddot

# a simple function to demonstrate, that it works
def ddot_mult(double[:]a, double[:]b):
    cdef int n=len(a)
    return call_ddot(&a[0], &b[0], n)

#-------------------------------------------------
# c-file, added so the example is complete    
cdef extern from *:
    """  
    ddot_t my_ddot;
    double call_ddot(double* a, double* b, int n){
        int one = 1;
        return my_ddot(&n, a, &one, b, &one);
    }
    """
    pass

А теперь ddot_mult может бытьused:

import numpy as np
a=np.arange(4, dtype=float)

ddot_mult(a,a)  # 14.0 as expected!

Преимущество этого подхода состоит в том, что с distutils не возникает проблем, и у вас есть гарантия использовать ту же функцию blas, что и для scipy.

Еще один бонус:Можно переключать используемый движок (mkl, open_blas или даже собственную реализацию) во время выполнения без необходимости перекомпиляции / перекомпоновки.

С другой стороны, есть некоторое дополнительное количество шаблонного кода, а такжеопасность того, что инициализация некоторых символов будет забыта.

0 голосов
/ 21 октября 2018

Я наконец-то разобрался с безобразным хаком для этого.Я не уверен, будет ли он работать всегда, но, по крайней мере, он работает для сочетаний Windows (mingw и visual studio), Linux, MKL и OpenBlas.Я все еще хотел бы знать, если есть лучшие альтернативы, но если нет, это сделает это:

Редактировать: Исправлено для Visual Studio

1) ИзменитьC-файлы для учета имен с подчеркиванием (делайте это для каждой вызываемой функции BLAS) - необходимо объявить каждую функцию дважды и добавить if для каждой

#ifndef ddot
double ddot_(int *N, double *DX, int *INCX, double *DY, int *INCY);
#define ddot(N, DX, INCX, DY, INCY) ddot_(N, DX, INCX, DY, INCY)
#endif

#ifndef daxpy
daxpy_(int *N, double *DA, double *DX, int *INCX, double *DY, int *INCY);
#define daxpy(N, DA, DX, INCX, DY, INCY) daxpy_(N, DA, DX, INCX, DY, INCY)
#endif

... etc

2) Извлечь путь к библиотеке из NumPy илиSciPy и добавьте его к аргументам ссылки.

3) Определите, является ли используемый компилятор визуальной студией, в этом случае аргументы компоновки сильно отличаются.

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext
import numpy
from sys import platform
import os

try:
    blas_path = numpy.distutils.system_info.get_info('blas')['library_dirs'][0]
except:
    if "library_dirs" in numpy.__config__.blas_mkl_info:
        blas_path = numpy.__config__.blas_mkl_info["library_dirs"][0]
    elif "library_dirs" in numpy.__config__.blas_opt_info:
        blas_path = numpy.__config__.blas_opt_info["library_dirs"][0]
    else:
        raise ValueError("Could not locate BLAS library.")


if platform[:3] == "win":
    if os.path.exists(os.path.join(blas_path, "mkl_rt.lib")):
        blas_file = "mkl_rt.lib"
    elif os.path.exists(os.path.join(blas_path, "mkl_rt.dll")):
        blas_file = "mkl_rt.dll"
    else:
        import re
        blas_file = [f for f in os.listdir(blas_path) if bool(re.search("blas", f))]
        if len(blas_file) == 0:
            raise ValueError("Could not locate BLAS library.")
        blas_file = blas_file[0]

elif platform[:3] == "dar":
    blas_file = "libblas.dylib"
else:
    blas_file = "libblas.so"

## /521686/python-distutils-kak-poluchit-kompilyator-kotoryi-budet-ispolzovatsya
class build_ext_subclass( build_ext ):
    def build_extensions(self):
        compiler = self.compiler.compiler_type
        if compiler == 'msvc': # visual studio
            for e in self.extensions:
                e.extra_link_args += [os.path.join(blas_path, blas_file)]
        else: # gcc
            for e in self.extensions:
                e.extra_link_args += ["-L"+blas_path, "-l:"+blas_file]
        build_ext.build_extensions(self)


setup(
    name  = "wrapped_cfun",
    packages = ["wrapped_cfun"],
    cmdclass = {'build_ext': build_ext_subclass},
    ext_modules = [Extension("wrapped_cfun.cython_part", sources=["pyfile.pyx"], include_dirs=[numpy.get_include()], extra_link_args=[])]
    )
...