使用可重定位的设备代码构建R包

时间:2016-10-24 18:54:32

标签: r cuda thrust cublas

我正在编写一个R包,它使用Thrust来处理内存分配并避免编写我自己的CUDA内核。

在某些情况下,我从设备代码而不是从主机代码调用cuBLAS例程。这会更改编译要求。虽然代码使用下面的nvcc命令进行编译,但可能需要显式调用主机链接器(g++)。如何修改当前构建过程以实现此目的?

我正在使用的步骤是:

  1. 使用max.o开关

  2. 编译包含设备可重定位代码的输出文件(-dc
  3. 创建一个库(libmax.a)以与

  4. 链接
  5. 使用somePackage.o开关

  6. 编译包含包装函数(-c)的输出文件
  7. 使用somePackage.so开关

  8. 创建链接到libmax.a的共享库(-shared

    如下所示的工作示例:

    iterator.h:这定义了一些类型,包括strideAccessor

    max.h:max.cu中的函数声明

    max.cu:定义一个函数,该函数查找维度为n的每个d级联数组中最大元素的索引。

    somePackage.cu:处理R / C ++接口的包装器

    $ cat iterator.h
    #ifndef ITER_H
    #define ITER_H
    
    #include <thrust/host_vector.h>
    #include <thrust/device_vector.h>
    #include <thrust/iterator/transform_iterator.h>
    #include <thrust/iterator/permutation_iterator.h>
    #include <thrust/tuple.h>
    #include <thrust/iterator/zip_iterator.h>
    
    typedef thrust::device_vector<int> ivec_d;
    typedef thrust::device_vector<double> fvec_d;
    typedef thrust::device_vector<int>::iterator intIter;
    typedef thrust::device_vector<double>::iterator realIter;
    typedef thrust::host_vector<int> ivec_h;
    typedef thrust::host_vector<double> fvec_h;
    
    typedef thrust::counting_iterator<int> countIter;
    
    //Used for generating rep( (1:len)*incr, times=infinity)
    struct stride: public thrust::unary_function<int, int>{
    
      int incr;
    
      __host__ __device__ stride(int incr=1): incr(incr){}
    
      __host__ __device__ int operator()(int x){
    
        return x*incr;
      }
    };
    
    typedef thrust::transform_iterator<stride, countIter> strideIter;
    typedef thrust::permutation_iterator<realIter, strideIter> strideAccessor;
    
    
    #endif
    
    $ cat max.h
    #include "iterator.h"
    
    void cublas_max(fvec_d &x, ivec_d &result, int n, int d);
    
    $ cat max.cu
    #include "iterator.h"
    #include <thrust/functional.h>
    #include <thrust/transform.h>
    #include <cublas_v2.h>
    #include <iostream>
    
    struct whichMax : thrust::unary_function<double, int>{
      int dim;
    
      __host__ __device__ whichMax(int dim): dim(dim){}
    
      __host__ __device__ int operator()(double &vec){
    
        cublasHandle_t handle;
        cublasCreate_v2(&handle);
        int incx=1, n = dim, result =0;
        double *vec_ptr = thrust::raw_pointer_cast(&vec);
    
        //find the first index of a maximal element
        cublasIdamax(handle, n, vec_ptr, incx, &result);
        cublasDestroy_v2(handle);
        return result;
      }
    };
    
    void cublas_max(fvec_d &x, ivec_d &result, int n, int d){
    
      stride f(d);
      strideIter siter = thrust::transform_iterator<stride, countIter>(thrust::make_counting_iterator<int>(0), f);
      strideAccessor stridex = thrust::permutation_iterator<realIter, strideIter>(x.begin(), siter);
    
      whichMax g(d);
    
      //find the index of maximum for each of n subvectors
      thrust::copy(result.begin(), result.end(), std::ostream_iterator<int>(std::cout, " "));
      std::cout << std::endl;
      thrust::transform(stridex, stridex + n, result.begin(),  g);
      thrust::copy(result.begin(), result.end(), std::ostream_iterator<int>(std::cout, " "));
      std::cout << std::endl;
    }
    
    $ cat somePackage.cu
    #include "iterator.h"
    #include "max.h"
    #include <thrust/host_vector.h>
    #include <thrust/device_vector.h>
    #include <R.h>
    #include <Rinternals.h>
    #include <Rmath.h>
    #include <iostream>
    
    extern "C" SEXP Rcublas_max(SEXP x, SEXP n, SEXP dim){
    
      double *xptr = REAL(x);
      int N = INTEGER(n)[0], D = INTEGER(n)[0];
    
      fvec_d dx(xptr, xptr+N*D);
      ivec_d dresult(N);
    
      cublas_max(dx, dresult, N, D);
    
      ivec_h hresult(N);
      thrust::copy(dresult.begin(), dresult.end(), hresult.begin());
    
      SEXP indices = PROTECT(allocVector(INTSXP, N));
    
      for(int i=0; i<N; ++i)
        INTEGER(indices)[i] = hresult[i];
    
      UNPROTECT(1);
      return indices;
    }
    
    $ make
    nvcc -dc -arch=sm_35 -Xcompiler -fPIC -lcublas_device -lcublas_device max.cu -o max.o
    nvcc -lib -arch=sm_35 -Xcompiler -fPIC -lcublas_device -lcublas_device max.o -o libmax.a
    nvcc -c -arch=sm_35 -Xcompiler -fPIC -lcublas_device somePackage.cu -lmax -I/home/emittman/src/R-3.3.1/builddir/include -I. -o somePackage.o
    nvcc -shared -arch=sm_35 -Xcompiler -fPIC -lcublas_device somePackage.o -I/home/emittman/src/R-3.3.1/builddir/include -I. -L. -lcublas_device -lmax -o somePackage.so
    ptxas info    : 'device-function-maxrregcount' is a BETA feature
    

1 个答案:

答案 0 :(得分:1)

我使用Rcpp创建了一个R包,从C ++共享库调用一些外部函数,然后调用CUDA内核来执行所需的计算。

您要在此处尝试将CUDA代码编译为静态库,然后将其链接到R包(它本身将编译为共享库)。我的方法与你的方法不同,我正在为我的方法提供描述,只是为了给你一个不同的想法。

这是一个简化的例子。

包含CUDA代码的共享库的kernels.cu:

__global__
void my_cuda_kernel( ... ) {
    // ......
}

包含CUDA代码的共享库的main.cu:

extern "C" {
    void do_cuda_work( ... ) {
        thrust::copy( ... );
        my_cuda_kernel <<< ... >>> ( ... );
    }
}
R包中的

package.cpp:

extern void do_cuda_work( ... );

// [[Rcpp::export]]
void call_cuda_code( ... ) {
    do_cuda_work( ... );
}

要将CUDA代码编译到共享库中,您需要使用:

nvcc -arch=sm_35 -dc ... kernels.cu -o kernels.o
nvcc -arch=sm_35 -dc ... main.cu -o main.o
nvcc --shared -arch=sm_35 ... kernels.o main.o ... libMyCUDALibrary.so

请注意,要使单独的编译工作,您需要为编译器和链接器指定-arch=sm_35,为编译器指定-dc。成功创建共享库后,将R包链接到它是非常简单的。 (但是,您可能需要在R包的Makevars文件夹下创建一个src文件来指定包含和库路径,也可以指定RPATH:

CXX_STD= CXX11
PKG_CPPFLAGS= -I../../../CPP/include
PKG_LIBS= -L../../../CPP/bin/Release -lMyCUDALibrary -Wl,-rpath=$$HOME/MyCUDALibrary/CPP/bin/Release `$(R_HOME)/bin/Rscript -e "Rcpp::LdFlags()"`