为什么 thrust::device_vector 似乎没有机会保存指向其他 device_vectors 的原始指针?
Why does thrust::device_vector not seem to have a chance to hold raw pointers to other device_vectors?
我有一个问题,我在其中找到了很多线索,但 none 明确回答了我的问题。
我正在尝试使用推力在 GPU 内核中创建一个多维数组。展平会很困难,因为所有维度都是非均匀的,我会升至 4D。现在我知道我不能有 device_vectors of 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 会工作,但是 return 错误的值。我这样访问它(同样,从内核中):
d_nFluidNeighborsCrossFluids[iterator1][iterator2][iterator3];
// Note: out of bounds indexing guaranteed to not happen, indexing is definitely right
问题是,为什么它 return 有错误的值?我认为它背后的逻辑应该有效,因为我的索引是正确的,指针应该是内核中的有效地址。
谢谢你抽出时间,祝你今天愉快。
编辑:
这是一个最小的可重现示例。由于某种原因,尽管与我的代码具有相同的结构,但这些值似乎是正确的,但 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();
}
你真的应该提供一个minimal, complete, verifiable/reproducible example;你的既不是最小的,也不是完整的,也不是可验证的。
不过,我会回答你的附带问题:
I know I cannot have device_vector
s of device_vector
s, for whichever underlying reason (explanation would be welcome)
虽然 device_vector
处理 GPU 上的一堆数据,但它是主机端数据结构 - 否则您将无法在主机端代码中使用它。在主机端,它包含的内容应该类似于:容量、元素大小、指向实际数据的设备端指针,以及更多信息。这类似于 std::vector
变量如何引用堆上的数据,但如果您在本地创建变量,我上面提到的字段将存在于堆栈中。
现在,通常无法从设备端访问位于主机内存中的设备向量的那些字段。在设备端代码中,您通常会使用指向 device_vector
管理的设备端数据的原始指针。
此外,请注意,如果您有 thrust::device_vector<T> v
,每次使用 operator[]
都意味着一堆单独的 CUDA 调用,用于将数据复制到设备或从设备中复制数据(除非有一些缓存正在进行洞)。所以你真的想避免在这种结构中使用方括号。
最后,请记住指针追逐可能会成为性能杀手,尤其是在 GPU 上。您可能需要考虑对您的数据结构进行一些调整,以使其易于扁平化。
一个device_vector是一个class定义。 class 有各种方法和运算符与之关联。允许您这样做的东西:
d_nFluidNeighborsCrossFluids[...]...;
是方括号运算符。该运营商是主机运营商(仅)。它在设备代码中不可用。像这样的问题导致一般性的说法是 "thrust::device_vector is not usable in device code." device_vector object 本身一般是不可用的。但是,如果您尝试通过原始指针访问它,它包含的数据可用于设备代码。
这是一个推力设备向量的示例,其中包含指向其他设备向量中包含的数据的指针数组。只要您不尝试使用 thrust::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
当您退出该区域时,该区域中定义的对象现在超出了范围。对于具有 constructors/destructors 的对象,这通常意味着 the destructor of the object will be called 当它超出范围时。对于 thrust::device_vector
(或 std::vector
),这将释放与该向量关联的任何底层存储。这不一定 "erase" 任何数据,但尝试使用该数据是非法的,在 C++ 中将被视为 UB(未定义行为)。
当您在范围内的区域内建立指向此类数据的指针,然后超出范围时,这些指针不再指向任何可以合法访问的内容,因此取消引用指针的尝试将是illegal/UB。您的代码正在执行此操作。 是,它似乎给出了正确的答案,因为在释放时实际上没有擦除任何东西,但代码设计是非法的,cuda-memcheck
将突出显示。
我想一个解决方法是将所有这些东西从内部花括号中拉出来,并将其放在 main
范围内,就像 d_nFluidNeighborsCrossFluids
device_vector 一样。但您可能还想重新考虑您的一般数据组织策略并扁平化您的数据。
我有一个问题,我在其中找到了很多线索,但 none 明确回答了我的问题。 我正在尝试使用推力在 GPU 内核中创建一个多维数组。展平会很困难,因为所有维度都是非均匀的,我会升至 4D。现在我知道我不能有 device_vectors of 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 会工作,但是 return 错误的值。我这样访问它(同样,从内核中):
d_nFluidNeighborsCrossFluids[iterator1][iterator2][iterator3];
// Note: out of bounds indexing guaranteed to not happen, indexing is definitely right
问题是,为什么它 return 有错误的值?我认为它背后的逻辑应该有效,因为我的索引是正确的,指针应该是内核中的有效地址。
谢谢你抽出时间,祝你今天愉快。
编辑: 这是一个最小的可重现示例。由于某种原因,尽管与我的代码具有相同的结构,但这些值似乎是正确的,但 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();
}
你真的应该提供一个minimal, complete, verifiable/reproducible example;你的既不是最小的,也不是完整的,也不是可验证的。
不过,我会回答你的附带问题:
I know I cannot have
device_vector
s ofdevice_vector
s, for whichever underlying reason (explanation would be welcome)
虽然 device_vector
处理 GPU 上的一堆数据,但它是主机端数据结构 - 否则您将无法在主机端代码中使用它。在主机端,它包含的内容应该类似于:容量、元素大小、指向实际数据的设备端指针,以及更多信息。这类似于 std::vector
变量如何引用堆上的数据,但如果您在本地创建变量,我上面提到的字段将存在于堆栈中。
现在,通常无法从设备端访问位于主机内存中的设备向量的那些字段。在设备端代码中,您通常会使用指向 device_vector
管理的设备端数据的原始指针。
此外,请注意,如果您有 thrust::device_vector<T> v
,每次使用 operator[]
都意味着一堆单独的 CUDA 调用,用于将数据复制到设备或从设备中复制数据(除非有一些缓存正在进行洞)。所以你真的想避免在这种结构中使用方括号。
最后,请记住指针追逐可能会成为性能杀手,尤其是在 GPU 上。您可能需要考虑对您的数据结构进行一些调整,以使其易于扁平化。
一个device_vector是一个class定义。 class 有各种方法和运算符与之关联。允许您这样做的东西:
d_nFluidNeighborsCrossFluids[...]...;
是方括号运算符。该运营商是主机运营商(仅)。它在设备代码中不可用。像这样的问题导致一般性的说法是 "thrust::device_vector is not usable in device code." device_vector object 本身一般是不可用的。但是,如果您尝试通过原始指针访问它,它包含的数据可用于设备代码。
这是一个推力设备向量的示例,其中包含指向其他设备向量中包含的数据的指针数组。只要您不尝试使用 thrust::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
当您退出该区域时,该区域中定义的对象现在超出了范围。对于具有 constructors/destructors 的对象,这通常意味着 the destructor of the object will be called 当它超出范围时。对于 thrust::device_vector
(或 std::vector
),这将释放与该向量关联的任何底层存储。这不一定 "erase" 任何数据,但尝试使用该数据是非法的,在 C++ 中将被视为 UB(未定义行为)。
当您在范围内的区域内建立指向此类数据的指针,然后超出范围时,这些指针不再指向任何可以合法访问的内容,因此取消引用指针的尝试将是illegal/UB。您的代码正在执行此操作。 是,它似乎给出了正确的答案,因为在释放时实际上没有擦除任何东西,但代码设计是非法的,cuda-memcheck
将突出显示。
我想一个解决方法是将所有这些东西从内部花括号中拉出来,并将其放在 main
范围内,就像 d_nFluidNeighborsCrossFluids
device_vector 一样。但您可能还想重新考虑您的一般数据组织策略并扁平化您的数据。