为什么push :: device_vector似乎没有机会保存指向其他device_vector的原始指针?

时间:2019-09-18 11:49:20

标签: multidimensional-array cuda thrust

我有一个问题,发现有很多线程,但是没有一个线程明确回答我的问题。 我正在尝试使用推力在GPU内核中使用多维数组。展平将很困难,因为所有尺寸都是不均匀的,而我使用的是4D。现在我知道我无法拥有device_vectors的device_vectors,无论出于何种根本原因(欢迎解释),因此我尝试通过原始指针进行操作。

我的理由是,原始指针指向GPU上的内存,为什么我还可以从内核内部访问它。因此,从技术上讲,我应该能够拥有一个device_vector,其中包含原始指针,所有指针都应该可以从GPU内访问。这样,我构造了以下代码:

thrust::device_vector<Vector3r*> d_fluidmodelParticlePositions(nModels);
thrust::device_vector<unsigned int***> d_allFluidNeighborParticles(nModels);
thrust::device_vector<unsigned int**> d_nFluidNeighborsCrossFluids(nModels);

for(unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
{
    FluidModel *model = sim->getFluidModelFromPointSet(fluidModelIndex);
    const unsigned int numParticles = model->numActiveParticles();

    thrust::device_vector<Vector3r> d_neighborPositions(model->getPositions().begin(), model->getPositions().end());
    d_fluidmodelParticlePositions[fluidModelIndex] = CudaHelper::GetPointer(d_neighborPositions);

    thrust::device_vector<unsigned int**> d_fluidNeighborIndexes(nModels);
    thrust::device_vector<unsigned int*> d_nNeighborsFluid(nModels);

    for(unsigned int pid = 0; pid < nModels; pid++)
    {
        FluidModel *fm_neighbor = sim->getFluidModelFromPointSet(pid);

        thrust::device_vector<unsigned int> d_nNeighbors(numParticles);
        thrust::device_vector<unsigned int*> d_neighborIndexesArray(numParticles);

        for(unsigned int i = 0; i < numParticles; i++)
        {
            const unsigned int nNeighbors = sim->numberOfNeighbors(fluidModelIndex, pid, i);        
            d_nNeighbors[i] = nNeighbors;

            thrust::device_vector<unsigned int> d_neighborIndexes(nNeighbors);

            for(unsigned int j = 0; j < nNeighbors; j++)
            {
                d_neighborIndexes[j] = sim->getNeighbor(fluidModelIndex, pid, i, j);
            }

            d_neighborIndexesArray[i] = CudaHelper::GetPointer(d_neighborIndexes);
        }

        d_fluidNeighborIndexes[pid] = CudaHelper::GetPointer(d_neighborIndexesArray);
        d_nNeighborsFluid[pid] = CudaHelper::GetPointer(d_nNeighbors);
    }

    d_allFluidNeighborParticles[fluidModelIndex] = CudaHelper::GetPointer(d_fluidNeighborIndexes);
    d_nFluidNeighborsCrossFluids[fluidModelIndex] = CudaHelper::GetPointer(d_nNeighborsFluid);
}

现在,编译器不会抱怨,但是从内核内部访问d_nFluidNeighborsCrossFluids可以正常工作,但是返回错误的值。我这样访问它(再次从内核内部):

d_nFluidNeighborsCrossFluids[iterator1][iterator2][iterator3];
// Note: out of bounds indexing guaranteed to not happen, indexing is definitely right

问题是,为什么它返回错误的值?我认为它背后的逻辑应该起作用,因为我的索引是正确的,并且指针应该是内核内部的有效地址。

感谢您的宝贵时间,祝您生活愉快。

编辑: 这是一个最小的可复制示例。出于某种原因,尽管这些值与我的代码具有相同的结构,但这些值仍然显示正确,但是cuda-memcheck会显示一些错误。取消对两条注释行的注释,将我引向我要解决的主要问题。这里的cuda-memcheck告诉我什么?

/* Part of this example has been taken from code of Robert Crovella 
   in a comment below */
#include <thrust/device_vector.h>
#include <stdio.h>

template<typename T>
static T* GetPointer(thrust::device_vector<T> &vector)
{
  return thrust::raw_pointer_cast(vector.data());
}

__global__ 
void k(unsigned int ***nFluidNeighborsCrossFluids, unsigned int ****allFluidNeighborParticles){

  const unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;

  if(i > 49)
    return;

  printf("i: %d nNeighbors: %d\n", i, nFluidNeighborsCrossFluids[0][0][i]);

  //for(int j = 0; j < nFluidNeighborsCrossFluids[0][0][i]; j++)
  //  printf("i: %d j: %d neighbors: %d\n", i, j, allFluidNeighborParticles[0][0][i][j]);
}


int main(){

  const unsigned int nModels = 2;
  const int numParticles = 50;

  thrust::device_vector<unsigned int**> d_nFluidNeighborsCrossFluids(nModels);
  thrust::device_vector<unsigned int***> d_allFluidNeighborParticles(nModels);

  for(unsigned int fluidModelIndex = 0; fluidModelIndex < nModels; fluidModelIndex++)
  {
    thrust::device_vector<unsigned int*> d_nNeighborsFluid(nModels);
    thrust::device_vector<unsigned int**> d_fluidNeighborIndexes(nModels);

    for(unsigned int pid = 0; pid < nModels; pid++)
    {

      thrust::device_vector<unsigned int> d_nNeighbors(numParticles);
      thrust::device_vector<unsigned int*> d_neighborIndexesArray(numParticles);

      for(unsigned int i = 0; i < numParticles; i++)
      {
        const unsigned int nNeighbors = i;        
        d_nNeighbors[i] = nNeighbors;

        thrust::device_vector<unsigned int> d_neighborIndexes(nNeighbors);

                for(unsigned int j = 0; j < nNeighbors; j++)
                {
                    d_neighborIndexes[j] = i + j;
        }
        d_neighborIndexesArray[i] = GetPointer(d_neighborIndexes);
      }
      d_nNeighborsFluid[pid] = GetPointer(d_nNeighbors);
      d_fluidNeighborIndexes[pid] = GetPointer(d_neighborIndexesArray);
    }
    d_nFluidNeighborsCrossFluids[fluidModelIndex] = GetPointer(d_nNeighborsFluid);
    d_allFluidNeighborParticles[fluidModelIndex] = GetPointer(d_fluidNeighborIndexes);

  }

  k<<<256, 256>>>(GetPointer(d_nFluidNeighborsCrossFluids), GetPointer(d_allFluidNeighborParticles));

  if (cudaGetLastError() != cudaSuccess) 
    printf("Sync kernel error: %s\n", cudaGetErrorString(cudaGetLastError()));

  cudaDeviceSynchronize();
}

2 个答案:

答案 0 :(得分:0)

您应该提供minimal, complete, verifiable/reproducible example;您的资料既不简单,也不完整,也不可验证。

但是,我将回答您的附带问题:

  

出于任何根本原因(欢迎解释),我知道我不能拥有device_vector中的device_vector

尽管device_vector考虑到GPU上的一堆数据,但它是主机端的数据结构-否则您将无法在主机端代码中使用它。在主机方面,它应具有的功能:容量,元素大小,指向实际数据的设备端指针以及更多信息。这类似于std::vector变量可能引用堆中数据的方式,但是如果您在本地创建变量,则我上面提到的字段将存在于堆栈中。

现在,通常无法从设备端访问位于主机存储器中的设备矢量的那些字段。在设备端代码中,通常会使用原始指针指向device_vector管理的设备端数据。

此外,请注意,如果您有thrust::device_vector<T> v,则每次使用operator[]意味着一堆单独的CUDA调用,以将数据复制到设备或从设备复制数据(除非在主机下进行了一些缓存) )。因此,您确实要避免在这种结构中使用方括号。

最后,请记住,追逐指针可能是性能的杀手,尤其是在GPU上。您可能需要考虑对数据结构进行某种程度的按摩,以使其易于展平。

答案 1 :(得分:0)

device_vector是一个类定义。该类具有与之关联的各种方法和运算符。可以让您执行此操作的事情:

d_nFluidNeighborsCrossFluids[...]...;

是方括号运算符。该运算符是主机运算符(仅)。在设备代码中不可用。诸如此类的问题引起了以下一般性陈述:“ thrust :: device_vector在设备代码中不可用”。 device_vector 对象本身通常不可用。但是,如果您尝试通过原始指针访问它,则其中包含的数据可以在设备代码中使用。

这里是推力设备矢量的示例,其中包含指向其他设备矢量中包含的数据的指针数组。只要您不尝试使用推力:: device_vector对象本身,该数据就可以在设备代码中使用:

$ cat t1509.cu
#include <thrust/device_vector.h>
#include <stdio.h>

template <typename T>
__global__ void k(T **data){

  printf("the first element of vector 1 is: %d\n", (int)(data[0][0]));
  printf("the first element of vector 2 is: %d\n", (int)(data[1][0]));
  printf("the first element of vector 3 is: %d\n", (int)(data[2][0]));
}


int main(){

  thrust::device_vector<int> vector_1(1,1);
  thrust::device_vector<int> vector_2(1,2);
  thrust::device_vector<int> vector_3(1,3);

  thrust::device_vector<int *> pointer_vector(3);
  pointer_vector[0] = thrust::raw_pointer_cast(vector_1.data());
  pointer_vector[1] = thrust::raw_pointer_cast(vector_2.data());
  pointer_vector[2] = thrust::raw_pointer_cast(vector_3.data());

  k<<<1,1>>>(thrust::raw_pointer_cast(pointer_vector.data()));
  cudaDeviceSynchronize();
}

$ nvcc -o t1509 t1509.cu
$ cuda-memcheck ./t1509
========= CUDA-MEMCHECK
the first element of vector 1 is: 1
the first element of vector 2 is: 2
the first element of vector 3 is: 3
========= ERROR SUMMARY: 0 errors
$

编辑:在您现在发布的mcve中,您指出普通的代码运行似乎可以提供正确的结果,但是当您使用cuda-memcheck时,会报告错误。您有一个一般的设计问题会导致这种情况。

在C ++中,当在花括号区域内定义对象时:

{
  {
    Object A;
    // object A is in-scope here
  }
  // object A is out-of-scope here
}
// object A is out of scope here
k<<<...>>>(anything that points to something in object A); // is illegal

,然后退出该区域,该区域内定义的对象现在不在范围内。对于具有构造函数/析构函数的对象,通常超出范围时表示the destructor of the object will be called。对于thrust::device_vector(或std::vector),这将取消分配与该向量关联的所有基础存储。不一定要“擦除”任何数据,但是尝试使用该数据是非法的,在C ++中将被视为UB(未定义行为)。

当您在范围内的区域中建立指向此类数据的指针,然后超出范围时,这些指针不再指向任何合法访问的内容,因此尝试取消引用该指针将是非法的/ UB。您的代码正在执行此操作。 ,它的确给出了正确的答案,因为在解除分配时实际上并未擦除任何内容,但是代码设计是非法的,cuda-memcheck会突出显示这一点。

我想一种解决方法是将所有这些东西从内部花括号中拉出,并将其放在main范围内,就像d_nFluidNeighborsCrossFluids device_vector一样。但是您可能还想重新考虑您的一般数据组织策略并整理数据。