推力:初始化推力变换函子时读取的无效 __global__

Thrust: invalid __global__ read when initializing thrust transform functor

我有一个基于 CRTP 的自定义矩阵库,经过测试,适用于动态矩阵:

#include <thrust/device_vector.h>
#include <assert.h>

namespace CML
{
template<class T, class Derived>

    template<class T, class Derived> class Matrix_Base {};
    template <class T>
    class Dynamic_Matrix : public Matrix_Base<T, Dynamic_Matrix<T>> 
    {
    private:
        size_t n_rows, n_cols;

        T* data;

        bool is_allocated;

        __host__ __device__ void allocate_data()
        {
              assert(n_rows > 0 && n_cols > 0);
              data = new T[n_rows * n_cols];
              is_allocated = true;
        }

        __host__ __device__ void deallocate_data() { delete[] data; data = nullptr; is_allocated = false; }

        __host__ __device__ void assign_data(const Dynamic_Matrix &other)
        {
             if (!other.is_allocated){ return; }

             n_rows = other.n_rows;
             n_cols = other.n_cols;

             if (!is_allocated){ allocate_data(); }
        
             printf("Dynamic matrix assign data, is_allocated? %d, is_other_allocated? %d \n", is_allocated, other.is_allocated);
             if (other.n_rows == 0 || other.n_cols == 0)
             {
                  printf("Error: n_rows == 0 or n_cols == 0! \n");
             } 
             for (size_t i = 0; i < n_rows; i++)
             {
                  for (size_t j = 0; j < n_cols; j++)
                  {
                        this->data[n_cols * i + j] = other.data[n_cols * i + j]; //<-- this line gives error
                  }
             }
        }

    public:
        
        __host__ __device__ Dynamic_Matrix() : n_rows(0), n_cols(0), data(nullptr), is_allocated(false) {}

        __host__ __device__ Dynamic_Matrix(const size_t n_rows, const size_t n_cols) :
        n_rows(n_rows), n_cols(n_cols), data(nullptr), is_allocated(false)
        {
            allocate_data();
        }

        __host__ __device__ Dynamic_Matrix(const Dynamic_Matrix &other):
        data(nullptr), is_allocated(false)
        {
            assign_data(other);
        }

        __host__ __device__ ~Dynamic_Matrix() { deallocate_data(); }

        __host__ __device__ Dynamic_Matrix& operator=(const Dynamic_Matrix &rhs)
        {
             if (this == &rhs)
             {
                 return *this;
             }
             deallocate_data(); 
             assign_data(rhs);
             return *this;
        }

        __host__ __device__ void resize(const size_t n_rows, const size_t n_cols)
        {
              assert(n_rows > 0 && n_cols > 0);
              *this = Dynamic_Matrix<T>(n_rows, n_cols);
        }
    };

    using MatrixXd = Dynamic_Matrix<double>;
};

在我的项目中一些类中使用,类似于这个简单的:

#include <thrust/device_vector.h>

class My_Class
{
private:

    CML::MatrixXd mat1;
    CML::MatrixXd mat2;
    CML::MatrixXd mat3;
    CML::MatrixXd mat4;

public:
    __host__ __device__ My_Class() { mat1.resize(3, 1); mat2.resize(3, 1); mat3.resize(3, 1); mat4.resize(3, 1); }
};

但是,当我尝试 运行 一个 thrust::transform 和一个仿函数(此处简化)时,如

#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/counting_iterator.h>

class myFunctor
{
private: 
    My_Class my_class;
public: 
    __host__ __device__ myFunctor() {}

    __device__ double operator()(const unsigned int n) { double ret = 0; return ret; }
};

int main()
{
    int n_cbs = 10;
    thrust::device_vector<double> cb_costs(n_cbs);

    thrust::counting_iterator<unsigned int> index_iter(0);
    
    thrust::transform(thrust::device, index_iter, index_iter + n_cbs, cb_costs.begin(), myFunctor());
    return 0;
}

这给我错误 Invalid __global__ read of size 8 ========= at 0x00001250 in /../dynamic_matrix.cuh:724:CML::Dynamic_Matrix<double>::assign_data(CML::Dynamic_Matrix<double> const &) ========= by thread (255,0,0) in block (0,0,0) ========= Address 0x55965a2ce530 is out of bounds 在应用程序上使用 cuda-memcheck 时。错误报告中的行是矩阵库中的this->data[n_cols * i + j] = other.data[n_cols * i + j];行。就调试而言,我还没有发现任何索引超出范围。 对问题有什么建议吗?在构造中注释掉矩阵对象使代码运行可用。

正如评论中指出的那样,问题在于您的 Dynamic_Matrix class 在主机和设备之间的可移植性。

原则上设计是可以的,只要你要么在主机内存中实例化使用,要么在设备内存中实例化使用。 new 运算符可以很好地用于主机和设备代码(尽管设备分配在运行时堆上进行,该堆必须是先验大小并且无法从主机 API 访问),但是 class 实例不可移植。数据分配在主机内存或设备内存中,并且在未分配数据的运行时 space 中不可访问。当您这样做时:

thrust::transform(thrust::device, index_iter, index_iter + n_cbs, cb_costs.begin(), myFunctor());

你隐含地要求将你的 myFunctor 实例复制到设备,这就是代码中断的地方。数据指针是主机指针,分配给主机 new 并且设备代码因无效指针错误而崩溃。

根据经验,任何你使用推力的东西都应该是 POD type。这意味着它们可以轻松复制到设备上,而不会出现便携性问题。您很可能需要重构代码以消除 newdelete 的使用,并将指针传递给将保存 class 数据的内存(您在设备上手动分配的内存)或在托管内存中)为此目的构造函数的版本。或者考虑通过模板专门化静态声明数据数组,这样您的 class 根本不需要动态内存分配。 Eigen 在其 CUDA 支持中使用了这种方法。

虽然这里不适用(因为 thrust 不使用仿函数的引用传递),请注意还有一个非常优雅的 design pattern 用于使用托管内存分配 class es 可以安全地通过引用传递。 类 以这种方式分配的内存可移植到主机和设备,但由于它们使用的托管内存的开销,它们的最终性能会较差。