sony / nnabla

Neural Network Libraries
https://nnabla.org/
Apache License 2.0
2.73k stars 334 forks source link

Can I use nbla::Array in a extenstion created by C Python API? #1247

Open twmht opened 9 months ago

twmht commented 9 months ago

Hi,

How to create nbla::Array in the c extension and return it to python side?

for example with numpy we can do

#include <Python.h>
#include <numpy/arrayobject.h>

static PyObject* create_numpy_array(PyObject* self, PyObject* args) {
    // Parse arguments if needed

    // Initialize Python and NumPy
    Py_Initialize();
    import_array();

    // Create a NumPy array
    npy_intp dims[2] = {3, 3};  // dimensions of the 2D array
    PyObject* myArray = PyArray_SimpleNew(2, dims, NPY_DOUBLE);

    // Access the array data
    double* data = (double*)PyArray_DATA(myArray);

    // Fill the array with some data
    for (npy_intp i = 0; i < dims[0]; ++i) {
        for (npy_intp j = 0; j < dims[1]; ++j) {
            data[i * dims[1] + j] = i + j;
        }
    }

    // Return the NumPy array
    return myArray;
}

static PyMethodDef methods[] = {
    {"create_numpy_array", create_numpy_array, METH_NOARGS, "Create and return a NumPy array"},
    {NULL, NULL, 0, NULL} // Sentinel
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "my_module", // Module name
    NULL,        // Module documentation, may be NULL
    -1,          // Size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
    methods
};

PyMODINIT_FUNC PyInit_my_module(void) {
    import_array();  // Must be called before PyModule_Create

    return PyModule_Create(&module);
}

but nbla::Array python interface is created by Cython, I am not sure how to use that in my c extension.

Any idea?

TomonobuTsujikawa commented 9 months ago

Our internal developer provided the following example.

Here is the sample how to export nd_array to python as numpy array:

#include <Python.h>
#include <numpy/arrayobject.h>
#include <nbla/nd_array.hpp>

static PyObject* create_numpy_array_from_nbla_array(PyObject* self, PyObject* args)
{
    // Parse arguments if needed

    // Initialize Python and NumPy
    Py_Initialize();
    import_array();

    //Suppose we have an NdArray
    nbla::Context cpu_ctx{{"cpu:float"}, "CpuCachedArray", "0"};
    nbla::Shape_t shape = {3, 3};
    nbla::NdArrayPtr nd_ptr = nbla::NdArray::create(shape);
    nbla::Array * array = nd_ptr->cast(nbla::get_dtype<double>(), cpu_ctx, false/*write_only*/);

    //Suppose we assigned in some place.
    double *p = array->pointer<double>();
    for (int i = 0; i < shape[0]; ++i) {
        for (int j = 0; j < shape[1]; ++j) {
            p[i * shape[1] + j] = i + j;
        }
    }

    //Suppose we read it in another place
    const nbla::Array *r_array = nd_ptr->get(nbla::get_dtype<double>(), cpu_ctx);
    const double *rp = r_array->const_pointer<double>();
    PyObject* myArray = PyArray_SimpleNewFromData(
        shape.size(),
        shape.data(),
        NPY_DOUBLE,
        (void*)rp);

    // Return the NumPy array
    return myArray;
}

static PyObject* create_numpy_array(PyObject* self, PyObject* args) {
    // Parse arguments if needed

    // Initialize Python and NumPy
    Py_Initialize();
    import_array();

    // Create a NumPy array
    npy_intp dims[2] = {3, 3};  // dimensions of the 2D array
    PyObject* myArray = PyArray_SimpleNew(2, dims, NPY_DOUBLE);

    // Access the array data
    double* data = (double*)PyArray_DATA(myArray);

    // Fill the array with some data
    for (npy_intp i = 0; i < dims[0]; ++i) {
        for (npy_intp j = 0; j < dims[1]; ++j) {
            data[i * dims[1] + j] = i + j;
        }
    }

    // Return the NumPy array
    return myArray;
}

static PyMethodDef methods[] = {
    {"create_numpy_array", create_numpy_array, METH_NOARGS, "Create and return a NumPy array"},
    {"create_numpy_array_from_nbla_array", create_numpy_array_from_nbla_array, METH_NOARGS, "Create and return a NumPy array from nd_array"},
    {NULL, NULL, 0, NULL} // Sentinel
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "my_module", // Module name
    NULL,        // Module documentation, may be NULL
    -1,          // Size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
    methods
};

PyMODINIT_FUNC PyInit_my_module(void) {
    import_array();  // Must be called before PyModule_Create

    return PyModule_Create(&module);
}

Here is CMakeLists.txt used to build above cpp file.

cmake_minimum_required(VERSION 3.14)
project(YourExtensionModule)

# Find the Python interpreter, libraries, and include directories
find_package(PythonLibs 3.2 REQUIRED)
find_package(PythonInterp 3.2 REQUIRED)

# Find NumPy headers
message(${PYTHON_EXECUTABLE})
execute_process(
  COMMAND "${PYTHON_EXECUTABLE}" -c "import numpy; print(numpy.get_include())"
  OUTPUT_VARIABLE NUMPY_INCLUDE_DIR
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

# Include directories for Python and NumPy
include_directories(${PYTHON_INCLUDE_DIRS} ${NUMPY_INCLUDE_DIR})

# Your source file(s). If you have more, add them here.
set(SOURCE_FILES pyarray.cpp)

# Enable C++11 (or another version if needed)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories("your_nnabla_include/nnabla/include")
link_directories("your_nnabla_sharedlib/nnabla/build/lib")

# Create a shared library that can be imported by Python
add_library(my_module MODULE ${SOURCE_FILES})

# Remove the 'lib' prefix from the output name to adhere to Python's naming convention
set_target_properties(my_module PROPERTIES PREFIX "")

# Link against Python libraries
target_link_libraries(my_module ${PYTHON_LIBRARIES} nnabla nnabla_utils)