diff --git a/R-package/.Rbuildignore b/R-package/.Rbuildignore new file mode 100644 index 000000000000..9355b92d1b90 --- /dev/null +++ b/R-package/.Rbuildignore @@ -0,0 +1,7 @@ +\.o$ +\.so$ +\.dll$ +^.*\.Rproj$ +^\.Rproj\.user$ + +README.md diff --git a/R-package/.gitignore b/R-package/.gitignore new file mode 100644 index 000000000000..df353c2153ff --- /dev/null +++ b/R-package/.gitignore @@ -0,0 +1,2 @@ +.Rhistory +R-package.Rproj diff --git a/R-package/mxnet/DESCRIPTION b/R-package/DESCRIPTION similarity index 63% rename from R-package/mxnet/DESCRIPTION rename to R-package/DESCRIPTION index 0a6bb132520f..6ca3a1ea44d8 100644 --- a/R-package/mxnet/DESCRIPTION +++ b/R-package/DESCRIPTION @@ -1,11 +1,13 @@ Package: mxnet Type: Package Title: MXNet -Version: 1.0 +Version: 0.5 Date: 2015-10-02 -Author: Your Name -Maintainer: Your Name +Author: Qiang Kou +Maintainer: Your Description: MXNet is a deep learning framework designed for both efficiency and flexibility. It allows you to mix the flavours of deep learning programs together to maximize the efficiency and your productivity. License: Apache-2.0 -Imports: Rcpp (>= 0.12.1) +URL: https://github.com/dmlc/mxnet +BugReports: https://github.com/dmlc/mxnet/issues +Imports: Rcpp (>= 0.11.1) LinkingTo: Rcpp diff --git a/R-package/mxnet/NAMESPACE b/R-package/NAMESPACE similarity index 100% rename from R-package/mxnet/NAMESPACE rename to R-package/NAMESPACE diff --git a/R-package/R/zzz.r b/R-package/R/zzz.r new file mode 100644 index 000000000000..3d2214ed4320 --- /dev/null +++ b/R-package/R/zzz.r @@ -0,0 +1 @@ +loadModule("mxnet", TRUE) diff --git a/R-package/README.md b/R-package/README.md new file mode 100644 index 000000000000..2994b1d08b0e --- /dev/null +++ b/R-package/README.md @@ -0,0 +1,11 @@ +MXNet R-Package +=============== +This is an on-going effort to support mxnet in R, stay tuned. + +Bleeding edge Installation +- First build ```../lib/libmxnet.so``` by following [Build Instruction](doc/build.md) +- Set the path to ```lib/libmxnet.so``` in ```LD_LIBRARY_PATH```, you can do it by modify ```~/.bashrc``` +```bash +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/path/to/lib/libmxnet.so +``` +- Type ```R CMD INSTALL R-package``` in the root folder. diff --git a/R-package/demo/00Index b/R-package/demo/00Index new file mode 100644 index 000000000000..059f16492fc7 --- /dev/null +++ b/R-package/demo/00Index @@ -0,0 +1 @@ +basic_ndarray Basic ndarray operations diff --git a/R-package/demo/basic_ndarray.R b/R-package/demo/basic_ndarray.R new file mode 100644 index 000000000000..13b9fcb3e31b --- /dev/null +++ b/R-package/demo/basic_ndarray.R @@ -0,0 +1 @@ +library(mxnet) diff --git a/R-package/install.sh b/R-package/install.sh deleted file mode 100755 index 759ce5881629..000000000000 --- a/R-package/install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -cp ../lib/libmxnet.a ./mxnet/src -cp ../dmlc-core/libdmlc.a mxnet/src - -mkdir -p ./mxnet/inst -mkdir -p ./mxnet/inst/include - -cp ../include/mxnet/c_api.h ./mxnet/inst/include/mxnet.h - -R CMD INSTALL mxnet diff --git a/R-package/mxnet/man/mxnet-package.Rd b/R-package/man/mxnet-package.Rd similarity index 100% rename from R-package/mxnet/man/mxnet-package.Rd rename to R-package/man/mxnet-package.Rd diff --git a/R-package/mxnet/R/zzz.r b/R-package/mxnet/R/zzz.r deleted file mode 100644 index a4eb1a463d6a..000000000000 --- a/R-package/mxnet/R/zzz.r +++ /dev/null @@ -1 +0,0 @@ -loadModule("mod_ndarray", TRUE) diff --git a/R-package/mxnet/src/Makevars b/R-package/mxnet/src/Makevars deleted file mode 100644 index 0a1b3ef666c8..000000000000 --- a/R-package/mxnet/src/Makevars +++ /dev/null @@ -1,6 +0,0 @@ -CXX_STD = CXX11 - -PKG_CPPFLAGS = -I../inst/include - -PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) -L./ -lmxnet -ldmlc - diff --git a/R-package/mxnet/src/mx_generated_function.hpp b/R-package/mxnet/src/mx_generated_function.hpp deleted file mode 100644 index da051e65c246..000000000000 --- a/R-package/mxnet/src/mx_generated_function.hpp +++ /dev/null @@ -1,57 +0,0 @@ - -#ifndef mx_generated_function_hpp -#define mx_generated_function_hpp - -#include "ndarray.hpp" -#include "mxnet.h" - -namespace Rcpp { - -class MxFunction : public CppFunction { - public: - MxFunction(FunctionHandle handle, const char * docstring = 0):handle(handle), - CppFunction(docstring) { - int ret = MXFuncGetInfo(handle, &name, &desc, &num_args, - &arg_names, &arg_types, &arg_descs); - // remove the '_' - if (name[0] == '_') - name++; - } - - SEXP operator()(SEXP * args) { - BEGIN_RCPP - NDArrayHandle out; - int ret = MXNDArrayCreateNone(&out); - NDArray res(out); - NDArrayHandle * use_vars = (NDArrayHandle *)malloc(num_args * sizeof(NDArrayHandle)); - for (int i = 0; i < num_args; i++) { - Rcpp::XPtr ptr(args[i]); - use_vars[i] = (*ptr.get()).handle; - } - - MXFuncInvoke(handle, use_vars, NULL, &res.handle); - return Rcpp::XPtr(new NDArray(res)); - END_RCPP - } - - inline int nargs() { return num_args; } - inline bool is_void() { return false; } - inline void signature(std::string& s, const char* name) { Rcpp::signature(s, name); } - - inline const char * get_name() {return name; }; - - inline DL_FUNC get_function_ptr() { return (DL_FUNC)NULL; } - private: - FunctionHandle handle; - const char * name; - const char * desc; - mx_uint num_args; - const char ** arg_names; - const char ** arg_types; - const char ** arg_descs; - -}; - -} - -#endif diff --git a/R-package/mxnet/src/ndarray.cpp b/R-package/mxnet/src/ndarray.cpp deleted file mode 100644 index d26be3732f2e..000000000000 --- a/R-package/mxnet/src/ndarray.cpp +++ /dev/null @@ -1,64 +0,0 @@ - -#include "ndarray.hpp" -#include "mx_generated_function.hpp" - -void save(SEXP sxptr, const std::string & filename) { - if (TYPEOF(sxptr) == 19) { - Rcpp::List data_lst(sxptr); - std::vector lst_names = data_lst.names(); - int num_args = data_lst.size(); - NDArrayHandle * handles = (NDArrayHandle *)malloc(num_args * sizeof(NDArrayHandle)); - std::vector keys; - for (int i = 0 ; i < num_args; i++) { - keys.push_back(lst_names[i].c_str()); - Rcpp::XPtr * ptr = new Rcpp::XPtr(sxptr); - handles[i] = (*(ptr->get())).handle; - } - MXNDArraySave(filename.c_str(), num_args, handles, &keys[0]); - } else if (TYPEOF(sxptr) == 22) { - Rcpp::XPtr ptr(sxptr); - NDArray data = *ptr.get(); - MXNDArraySave(filename.c_str(), 1, &data.handle, NULL); - } else { - Rcpp::Rcerr << "only NDArray or list of NDArray" << std::endl; - } -} - -SEXP load(const std::string & filename) { - mx_uint out_size; - NDArrayHandle* out_arr; - mx_uint out_name_size; - const char** out_names; - MXNDArrayLoad(filename.c_str(), &out_size, &out_arr, &out_name_size, &out_names); - std::vector lst_names(out_size); - Rcpp::List out(out_size); - for (int i = 0; i < out_size; i++) { - out[i] = Rcpp::XPtr(new NDArray(out_arr[i])); - } - - for (int i = 0; i < out_size; i++) { - if (out_name_size != 0) - lst_names[i] = out_names[i]; - else - lst_names[i] = "X" + std::to_string(i); - } - out.attr("names") = lst_names; - return out; -} - -RCPP_MODULE(mod_ndarray) { - using namespace Rcpp; - function("load_ndarray", &load); - function("save_ndarray", &save); - - mx_uint out_size; - FunctionHandle * out_array; - int ret = MXListFunctions(&out_size, &out_array); - - for (mx_uint i = 0; i < out_size; i++) { - MxFunction * fun = new MxFunction(out_array[i]); - _rcpp_module_mod_ndarray.Add(fun->get_name(), fun); - } - -} - diff --git a/R-package/mxnet/src/ndarray.hpp b/R-package/mxnet/src/ndarray.hpp deleted file mode 100644 index 937e9cef3da5..000000000000 --- a/R-package/mxnet/src/ndarray.hpp +++ /dev/null @@ -1,20 +0,0 @@ - -#ifndef Rcpp_ndarray_hpp -#define Rcpp_ndarray_hpp - -#include -#include "mxnet.h" - -class NDArray { - public: - NDArray(NDArrayHandle handle, bool writable = true): handle(handle), writable(writable){} - NDArray(const NDArray& n):handle(n.handle), writable(n.writable){} - NDArrayHandle handle; - bool writable; -}; - -SEXP load(const std::string & filename); - -void save(SEXP data, const std::string& filename); - -#endif diff --git a/R-package/src/Makevars b/R-package/src/Makevars new file mode 100644 index 000000000000..c7f7d3548e3d --- /dev/null +++ b/R-package/src/Makevars @@ -0,0 +1,24 @@ +# _*_ mode: makefile; _*_ +PKGROOT=../../ + +# This file is only used for compilation from github +# It will be replaced by more formal Rpackage structure +# Where PKGROOT moved to root directory + +.PHONY: all mxlib +all: $(SHLIB) + +$(SHLIB): mxlib +mxlib: + cd $(PKGROOT); make CXX="$(CXX)"; cd - + cp $(PKGROOT)/lib/libmxnet.so libmxnet.so + mkdir -p ../inst + mkdir -p ../inst/libs + cp $(PKGROOT)/lib/libmxnet.so ../inst/libs/libmxnet.so + +# Need to add libmxnet.so to LD_LIBRARY_PATH +# TODO: make it install together with mxnet.so +LINKMXNET = -L../inst/libs -lmxnet + +PKG_CPPFLAGS = -I$(PKGROOT)/include -I$(PKGROOT)/dmlc-core/include +PKG_LIBS = $(LINKMXNET) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) diff --git a/R-package/src/base.h b/R-package/src/base.h new file mode 100644 index 000000000000..f8eacee20fe4 --- /dev/null +++ b/R-package/src/base.h @@ -0,0 +1,33 @@ +/*! + * Copyright (c) 2015 by Contributors + * \file base.h + * \brief Rcpp interface of MXNet + */ +#ifndef MXNET_RCPP_BASE_H_ +#define MXNET_RCPP_BASE_H_ + +#include +#include +// to be removed +#include + +namespace mxnet { +namespace R { + +// change to Rcpp::cerr later, for compatiblity of older version for now +#define RLOG_FATAL LOG(FATAL) + +/*! + * \brief protected MXNet C API call, report R error if happens. + * \param func Expression to call. + */ +#define MX_CALL(func) \ + { \ + int e = (func); \ + if (e != 0) { \ + RLOG_FATAL << MXGetLastError(); \ + } \ + } +} // namespace Rcpp +} // namespace mxnet +#endif // MXNET_RCPP_BASE_H_ diff --git a/R-package/src/mxnet.cc b/R-package/src/mxnet.cc new file mode 100644 index 000000000000..15b7fc0100bc --- /dev/null +++ b/R-package/src/mxnet.cc @@ -0,0 +1,8 @@ +#include +#include "./ndarray.h" + +RCPP_MODULE(mxnet) { + using namespace mxnet::R; // NOLINT(*) + NDArray::InitRcppModule(); + NDArrayFunction::InitRcppModule(); +} diff --git a/R-package/src/ndarray.cc b/R-package/src/ndarray.cc new file mode 100644 index 000000000000..0d1edcbda035 --- /dev/null +++ b/R-package/src/ndarray.cc @@ -0,0 +1,152 @@ +#include +#include +#include "./base.h" +#include "./ndarray.h" + +namespace mxnet { +namespace R { // NOLINT(*) + +void NDArray::Save(SEXP sxptr, const std::string& filename) { + // TODO(KK) add constant instead of integer + if (TYPEOF(sxptr) == 19) { + Rcpp::List data_lst(sxptr); + std::vector lst_names = data_lst.names(); + size_t num_args = data_lst.size(); + std::vector handles(num_args); + std::vector keys(num_args); + + for (int i = 0 ; i < data_lst.size(); ++i) { + keys[i] = lst_names[i].c_str(); + SEXP arr = data_lst[i]; + handles[i] = Rcpp::XPtr(arr)->handle_; + } + MX_CALL(MXNDArraySave(filename.c_str(), num_args, + dmlc::BeginPtr(handles), + dmlc::BeginPtr(keys))); + } else if (TYPEOF(sxptr) == 22) { + Rcpp::XPtr ptr(sxptr); + MX_CALL(MXNDArraySave(filename.c_str(), 1, &(ptr->handle_), NULL)); + } else { + RLOG_FATAL << "only NDArray or list of NDArray" << std::endl; + } +} + +SEXP NDArray::Load(const std::string& filename) { + mx_uint out_size; + NDArrayHandle* out_arr; + mx_uint out_name_size; + const char** out_names; + MX_CALL(MXNDArrayLoad(filename.c_str(), + &out_size, &out_arr, + &out_name_size, &out_names)); + Rcpp::List out(out_size); + for (mx_uint i = 0; i < out_size; ++i) { + out[i] = Rcpp::XPtr(new NDArray(out_arr[i])); + } + if (out_name_size != 0) { + std::vector lst_names(out_size); + for (mx_uint i = 0; i < out_size; ++i) { + lst_names[i] = out_names[i]; + } + out.attr("names") = lst_names; + } + return out; +} + +NDArrayFunction::NDArrayFunction(FunctionHandle handle) + : handle_(handle) { + // initialize the docstring + { + const char *name; + const char *description; + mx_uint num_args; + const char **arg_names; + const char **arg_type_infos; + const char **arg_descriptions; + MX_CALL(MXFuncGetInfo(handle, &name, &description, &num_args, + &arg_names, &arg_type_infos, &arg_descriptions)); + // set function name + name_ = name; + // dostring: generate python style for now, change to R style later + std::ostringstream os; + os << description << "\n\n" + << "Parameters\n" + << "----------\n"; + for (mx_uint i = 0; i < num_args; ++i) { + os << " " << arg_names[i] << " : " << arg_type_infos[i] << "\n" + << " " << arg_descriptions[i] << "\n"; + } + os << "Returns\n" + << "-------\n" + << "out : NDArray\n" + << " The output result of the function"; + // set the dostring + this->docstring = os.str(); + } + // initialize the function information + { + const int kNDArrayArgBeforeScalar = 1; + const int kAcceptEmptyMutateTarget = 1 << 2; + int type_mask; + MX_CALL(MXFuncDescribe( + handle, &num_use_vars_, &num_scalars_, + &num_mutate_vars_, &type_mask)); + if ((type_mask & kNDArrayArgBeforeScalar) != 0) { + begin_use_vars_ = 0; + begin_scalars_ = num_use_vars_; + } else { + begin_scalars_ = num_scalars_; + begin_scalars_ = 0; + } + num_args_ = num_use_vars_ + num_scalars_; + accept_empty_out_ = ((type_mask & kAcceptEmptyMutateTarget) != 0); + } +} + +SEXP NDArrayFunction::operator() (SEXP* args) { + BEGIN_RCPP; + if (!accept_empty_out_) { + RLOG_FATAL << "not yet support mutate target"; + } + NDArrayHandle ohandle; + MX_CALL(MXNDArrayCreateNone(&ohandle)); + std::vector scalars(num_scalars_); + std::vector use_vars(num_use_vars_); + + for (mx_uint i = 0; i < num_scalars_; ++i) { + // better to use Rcpp cast? + scalars[i] = (REAL)(args[begin_scalars_ + i])[0]; + } + for (mx_uint i = 0; i < num_use_vars_; ++i) { + use_vars[i] = Rcpp::XPtr(args[begin_use_vars_ + i])->handle_; + } + MX_CALL(MXFuncInvoke(handle_, + dmlc::BeginPtr(use_vars), + dmlc::BeginPtr(scalars), + &ohandle)); + return Rcpp::XPtr(new NDArray(ohandle)); + END_RCPP; +} + +// register normal function. +void NDArray::InitRcppModule() { + using namespace Rcpp; // NOLINT(*) + function("mx.nd.load", &NDArray::Load); + function("mx.nd.save", &NDArray::Save); +} + +void NDArrayFunction::InitRcppModule() { + Rcpp::Module* scope = ::getCurrentScope(); + if (scope == NULL) { + RLOG_FATAL << "Init Module need to be called inside scope"; + } + mx_uint out_size; + FunctionHandle *arr; + MX_CALL(MXListFunctions(&out_size, &arr)); + for (int i = 0; i < out_size; ++i) { + NDArrayFunction *f = new NDArrayFunction(arr[i]); + scope->Add(f->get_name(), f); + } +} +} // namespace Rcpp +} // namespace mxnet diff --git a/R-package/src/ndarray.h b/R-package/src/ndarray.h new file mode 100644 index 000000000000..174d0568a316 --- /dev/null +++ b/R-package/src/ndarray.h @@ -0,0 +1,105 @@ +/*! + * Copyright (c) 2015 by Contributors + * \file ndarray.h + * \brief Rcpp NDArray interface of MXNet + */ +#ifndef MXNET_RCPP_NDARRAY_H_ +#define MXNET_RCPP_NDARRAY_H_ + +#include +#include + +namespace mxnet { +namespace R { // NOLINT(*) + +// forward declare NDArrayFunction +class NDArrayFunction; + +class NDArray { + public: + /*! \brief default constructor */ + NDArray() {} + /*! + * \brief construct NDArray from handle + * \param handle the NDArrayHandle needed for output. + * \param writable Whether the NDArray is writable or not. + */ + explicit NDArray(NDArrayHandle handle, + bool writable = true) + : handle_(handle), writable_(writable) {} + /*! + * \brief Load a list of ndarray from the file. + * \param filename the name of the file. + * \return R List of NDArrays + */ + static SEXP Load(const std::string& filename); + /*! + * \brief Save a list of NDArray to file. + * \param data R List of NDArrays + * \param filename The name of the file to be saved. + */ + static void Save(SEXP data, const std::string& filename); + /*! \brief static function to initialize the Rcpp functions */ + static void InitRcppModule(); + + private: + // declare friend class + friend class NDArrayFunction; + /*! \brief handle to the NDArray */ + NDArrayHandle handle_; + /*! \brief Whether the NDArray is writable */ + bool writable_; +}; + +/*! \brief The NDArray functions to be invoked */ +class NDArrayFunction : public ::Rcpp::CppFunction { + public: + virtual SEXP operator() (SEXP * args); + + virtual int nargs() { + return num_args_; + } + + virtual bool is_void() { + return false; + } + + virtual void signature(std::string& s, const char* name) { + ::Rcpp::signature< ::Rcpp::void_type >(s, name); + } + + virtual const char* get_name() { + return name_.c_str(); + } + + virtual DL_FUNC get_function_ptr() { + return (DL_FUNC)NULL; // NOLINT(*) + } + /*! \brief static function to initialize the Rcpp functions */ + static void InitRcppModule(); + private: + // make constructor private + explicit NDArrayFunction(FunctionHandle handle); + + /*! \brief internal functioon handle. */ + FunctionHandle handle_; + // name of the function + std::string name_; + // beginning position of use vars + mx_uint begin_use_vars_; + // number of use variable + mx_uint num_use_vars_; + // beginning position of scalars + mx_uint begin_scalars_; + // number of scalars + mx_uint num_scalars_; + // number of mutate variables + mx_uint num_mutate_vars_; + // number of arguments + mx_uint num_args_; + // whether it accept empty output + bool accept_empty_out_; +}; +} // namespace Rcpp +} // namespace mxnet +#endif // MXNET_RCPP_NDARRAY_H_