gpt4 book ai didi

cuda - CUDA内核中的Heisenbug,全局内存访问

转载 作者:行者123 更新时间:2023-12-04 23:00:22 25 4
gpt4 key购买 nike

大约两年前,我编写了一个内核,用于同时处理多个数值网格。出现了一些非常奇怪的行为,导致错误的结果。当利用内核内部的printf()语句查找bug时,该bug消失了。

由于截止日期的限制,我一直保持这种方式,尽管最近我发现这不是合适的编码风格。因此,我重新审视了内核,并将其归结为您在下面看到的内容。

__launch_bounds__(672, 2)
__global__ void heisenkernel(float *d_u, float *d_r, float *d_du, int radius,
int numNodesPerGrid, int numBlocksPerSM, int numGridsPerSM, int numGrids)
{
__syncthreads();
int id_sm = blockIdx.x / numBlocksPerSM; // (arbitrary) ID of Streaming Multiprocessor (SM) this thread works upon - (constant over lifetime of thread)
int id_blockOnSM = blockIdx.x % numBlocksPerSM; // Block number on this specific SM - (constant over lifetime of thread)
int id_r = id_blockOnSM * (blockDim.x - 2*radius) + threadIdx.x - radius; // Grid point number this thread is to work upon - (constant over lifetime of thread)
int id_grid = id_sm * numGridsPerSM; // Grid ID this thread is to work upon - (not constant over lifetime of thread)

while(id_grid < numGridsPerSM * (id_sm + 1)) // this loops over numGridsPerSM grids
{
__syncthreads();
int id_numInArray = id_grid * numNodesPerGrid + id_r; // Entry in array this thread is responsible for (read and possibly write) - (not constant over lifetime of thread)
float uchange = 0.0f;
//uchange = 1.0f; // if this line is uncommented, results will be computed correctly ("Solution 1")
float du = 0.0f;

if((threadIdx.x > radius-1) && (threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids))
{
if (id_r == 0) // FO-forward difference
du = (d_u[id_numInArray+1] - d_u[id_numInArray])/(d_r[id_numInArray+1] - d_r[id_numInArray]);
else if (id_r == numNodesPerGrid - 1) // FO-rearward difference
du = (d_u[id_numInArray] - d_u[id_numInArray-1])/(d_r[id_numInArray] - d_r[id_numInArray-1]);
else if (id_r == 1 || id_r == numNodesPerGrid - 2) //SO-central difference
du = (d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1]);
else if(id_r > 1 && id_r < numNodesPerGrid - 2)
du = d_fourpoint_constant * ((d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1])) + (1-d_fourpoint_constant) * ((d_u[id_numInArray+2] - d_u[id_numInArray-2])/(d_r[id_numInArray+2] - d_r[id_numInArray-2]));
else
du = 0;
}

__syncthreads();
if((threadIdx.x > radius-1 && threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids))
{
d_u[ id_numInArray] = d_u[id_numInArray] * uchange; // if this line is commented out, results will be computed correctly ("Solution 2")
d_du[ id_numInArray] = du;
}

__syncthreads();
++id_grid;
}


该内核为许多数字一维网格计算所有网格点上某些值的导数。

注意事项:(请参阅底部的完整代码库)




一个网格由1300个网格点组成
每个网格必须由两个块处理(由于内存/寄存器限制)
每个块先后在37个网格上工作(或更优:对半网格,while循环负责)
每个线程负责每个网格中的相同网格点
对于要计算的导数,线程需要访问下四个网格点的数据
为了使块彼此独立,在网格上引入了一个小的重叠(虽然只有一个线程正在向它们写入,但每个网格的网格点666、667、668、669是由来自不同块的两个线程读取的) ,就是发生问题的地方就是这种重叠)
由于沸腾过程,块每一侧的两个线程不进行计算,在原始情况下,它们负责将相应的网格值写入共享内存


网格的值存储在 u_arrdu_arrr_arr(及其对应的设备数组 d_ud_dud_r)中。
每个网格在这些阵列的每个阵列中占据1300个连续值。
内核中的while循环为每个块迭代37个网格。

为了评估内核的工作状况,每个网格都使用完全相同的值进行初始化,因此确定性程序将为每个网格产生相同的结果。
我的代码不会发生这种情况。

Heisenbug的怪异之处:

我将网格0的计算值与其他每个网格进行了比较,并且在重叠处(网格点666-669)存在差异,尽管不一致。有些网格具有正确的值,有些则没有。连续两次运行会将不同的网格标记为错误。
首先想到的是,在此重叠处的两个线程尝试并发写入内存,尽管事实并非如此(我检查了...并重新检查了)。

注释或取消注释行或使用 printf()进行调试将改变
程序的结果也是如此:当“询问”负责所讨论的网格点的线程时,它们告诉我一切都很好,并且实际上是正确的。一旦我强制线程打印出其变量,它们将被正确计算(更重要的是:存储)。
使用Nsight Eclipse进行调试也是如此。

Memcheck / Racecheck:

尽管即使使用这些工具之一也可以影响结果的正确性,但是cuda-memcheck(memcheck和racecheck)没有报告内存/争用问题。
Valgrind给出了一些警告,尽管我认为它们与CUDA API有关,但我不能影响它,并且似乎与我的问题无关。

(更新)
如前所述, cuda-memcheck --tool racecheck仅适用于共享内存争用条件,而当前的问题具有 d_u的争用条件,即全局内存。

测试环境:

原始内核已在不同的CUDA设备上进行了测试,并具有不同的计算功能(2.0、3.0和3.5),并且该错误在每种配置(以某种形式或另一种形式)中均会显示。

我的(主要)测试系统如下:


2 x GTX 460,在运行X服务器的GPU和
另一个
驱动程序版本:340.46
Cuda工具包6.5
Linux Kernel 3.11.0-12-generic(Linux Mint 16-Xfce)


解决状态:

到目前为止,我非常确定某些内存访问是罪魁祸首,也许是编译器进行的某些优化或未初始化值的使用,并且我显然不了解某些基本的CUDA范例。
内核中的 printf()语句(通过一些黑魔法也必须利用设备和主机内存)和memcheck算法(cuda-memcheck和valgrind)会影响这一事实
行为点指向相同方向。

对于这个稍微复杂的内核,我感到很抱歉,但是我尽力将原始内核煮沸,并尽我所能地调用它,这是我所能做到的。到目前为止,我已经学会了欣赏这个问题,并且我期待了解这里发生的事情。

代码中标记了两个“解决方案”,它们迫使内核按预期方式工作。

(更新)如以下正确答案中所述,我的代码存在问题是线程块边界处的竞争状况。由于每个网格上有两个块,因此无法保证哪个块首先起作用,从而导致下面概述的行为。当使用代码中提到的“解决方案1”时,它也解释了正确的结果,因为当 d_u时输入/输出值 uchange = 1.0不会改变。

一种简单的解决方案是将该内核分为两个内核,一个计算 d_u,另一个计算派生的 d_du。尽管我不知道如何使用 -arch=sm_20完成此操作,但最好仅执行一次内核调用而不是两次。尽管第二次内核调用的开销可以忽略不计,但使用 -arch=sm_35可能可以使用动态并行性来实现。

heisenbug.cu:

#include <cuda.h>
#include <cuda_runtime.h>
#include <stdio.h>

const float r_sol = 6.955E8f;
__constant__ float d_fourpoint_constant = 0.2f;

__launch_bounds__(672, 2)
__global__ void heisenkernel(float *d_u, float *d_r, float *d_du, int radius,
int numNodesPerGrid, int numBlocksPerSM, int numGridsPerSM, int numGrids)
{
__syncthreads();
int id_sm = blockIdx.x / numBlocksPerSM; // (arbitrary) ID of Streaming Multiprocessor (SM) this thread works upon - (constant over lifetime of thread)
int id_blockOnSM = blockIdx.x % numBlocksPerSM; // Block number on this specific SM - (constant over lifetime of thread)
int id_r = id_blockOnSM * (blockDim.x - 2*radius) + threadIdx.x - radius; // Grid point number this thread is to work upon - (constant over lifetime of thread)
int id_grid = id_sm * numGridsPerSM; // Grid ID this thread is to work upon - (not constant over lifetime of thread)

while(id_grid < numGridsPerSM * (id_sm + 1)) // this loops over numGridsPerSM grids
{
__syncthreads();
int id_numInArray = id_grid * numNodesPerGrid + id_r; // Entry in array this thread is responsible for (read and possibly write) - (not constant over lifetime of thread)
float uchange = 0.0f;
//uchange = 1.0f; // if this line is uncommented, results will be computed correctly ("Solution 1")
float du = 0.0f;

if((threadIdx.x > radius-1) && (threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids))
{
if (id_r == 0) // FO-forward difference
du = (d_u[id_numInArray+1] - d_u[id_numInArray])/(d_r[id_numInArray+1] - d_r[id_numInArray]);
else if (id_r == numNodesPerGrid - 1) // FO-rearward difference
du = (d_u[id_numInArray] - d_u[id_numInArray-1])/(d_r[id_numInArray] - d_r[id_numInArray-1]);
else if (id_r == 1 || id_r == numNodesPerGrid - 2) //SO-central difference
du = (d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1]);
else if(id_r > 1 && id_r < numNodesPerGrid - 2)
du = d_fourpoint_constant * ((d_u[id_numInArray+1] - d_u[id_numInArray-1])/(d_r[id_numInArray+1] - d_r[id_numInArray-1])) + (1-d_fourpoint_constant) * ((d_u[id_numInArray+2] - d_u[id_numInArray-2])/(d_r[id_numInArray+2] - d_r[id_numInArray-2]));
else
du = 0;
}

__syncthreads();
if((threadIdx.x > radius-1 && threadIdx.x < blockDim.x - radius) && (id_r < numNodesPerGrid) && (id_grid < numGrids))
{
d_u[ id_numInArray] = d_u[id_numInArray] * uchange; // if this line is commented out, results will be computed correctly ("Solution 2")
d_du[ id_numInArray] = du;
}

__syncthreads();
++id_grid;
}
}

bool gridValuesEqual(float *matarray, uint id0, uint id1, const char *label, int numNodesPerGrid){

bool retval = true;
for(uint i=0; i<numNodesPerGrid; ++i)
if(matarray[id0 * numNodesPerGrid + i] != matarray[id1 * numNodesPerGrid + i])
{
printf("value %s at position %u of grid %u not equal that of grid %u: %E != %E, diff: %E\n",
label, i, id0, id1, matarray[id0 * numNodesPerGrid + i], matarray[id1 * numNodesPerGrid + i],
matarray[id0 * numNodesPerGrid + i] - matarray[id1 * numNodesPerGrid + i]);
retval = false;
}
return retval;
}

int main(int argc, const char* argv[])
{
float *d_u;
float *d_du;
float *d_r;

float *u_arr;
float *du_arr;
float *r_arr;

int numNodesPerGrid = 1300;
int numBlocksPerSM = 2;
int numGridsPerSM = 37;
int numSM = 7;
int TPB = 672;
int radius = 2;
int numGrids = 259;
int memsize_grid = sizeof(float) * numNodesPerGrid;

int numBlocksPerGrid = numNodesPerGrid / (TPB - 2 * radius) + (numNodesPerGrid%(TPB - 2 * radius) == 0 ? 0 : 1);

printf("---------------------------------------------------------------------------\n");
printf("--- Heisenbug Extermination Tracker ---------------------------------------\n");
printf("---------------------------------------------------------------------------\n\n");

cudaSetDevice(0);
cudaDeviceReset();

cudaMalloc((void **) &d_u, memsize_grid * numGrids);
cudaMalloc((void **) &d_du, memsize_grid * numGrids);
cudaMalloc((void **) &d_r, memsize_grid * numGrids);

u_arr = new float[numGrids * numNodesPerGrid];
du_arr = new float[numGrids * numNodesPerGrid];
r_arr = new float[numGrids * numNodesPerGrid];

for(uint k=0; k<numGrids; ++k)
for(uint i=0; i<numNodesPerGrid; ++i)
{
uint index = k * numNodesPerGrid + i;

if (i < 585)
r_arr[index] = i * (6000.0f);
else
{
if (i == 585)
r_arr[index] = r_arr[index - 1] + 8.576E-6f * r_sol;
else
r_arr[index] = r_arr[index - 1] + 1.02102f * ( r_arr[index - 1] - r_arr[index - 2] );
}

u_arr[index] = 1E-10f * (i+1);
du_arr[index] = 0.0f;
}

/*
printf("\n\nbefore kernel start\n\n");
for(uint k=0; k<numGrids; ++k)
printf("matrix->du_arr[k*paramH.numNodes + 668]:\t%E\n", du_arr[k*numNodesPerGrid + 668]);//*/

bool equal = true;
for(int k=1; k<numGrids; ++k)
{
equal &= gridValuesEqual(u_arr, 0, k, "u", numNodesPerGrid);
equal &= gridValuesEqual(du_arr, 0, k, "du", numNodesPerGrid);
equal &= gridValuesEqual(r_arr, 0, k, "r", numNodesPerGrid);
}

if(!equal)
printf("Input values are not identical for different grids!\n\n");
else
printf("All grids contain the same values at same grid points.!\n\n");

cudaMemcpy(d_u, u_arr, memsize_grid * numGrids, cudaMemcpyHostToDevice);
cudaMemcpy(d_du, du_arr, memsize_grid * numGrids, cudaMemcpyHostToDevice);
cudaMemcpy(d_r, r_arr, memsize_grid * numGrids, cudaMemcpyHostToDevice);

printf("Configuration:\n\n");
printf("numNodesPerGrid:\t%i\nnumBlocksPerSM:\t\t%i\nnumGridsPerSM:\t\t%i\n", numNodesPerGrid, numBlocksPerSM, numGridsPerSM);
printf("numSM:\t\t\t\t%i\nTPB:\t\t\t\t%i\nradius:\t\t\t\t%i\nnumGrids:\t\t\t%i\nmemsize_grid:\t\t%i\n", numSM, TPB, radius, numGrids, memsize_grid);
printf("numBlocksPerGrid:\t%i\n\n", numBlocksPerGrid);
printf("Kernel launch parameters:\n\n");
printf("moduleA2_3<<<%i, %i, %i>>>(...)\n\n", numBlocksPerSM * numSM, TPB, 0);
printf("Launching Kernel...\n\n");

heisenkernel<<<numBlocksPerSM * numSM, TPB, 0>>>(d_u, d_r, d_du, radius, numNodesPerGrid, numBlocksPerSM, numGridsPerSM, numGrids);
cudaDeviceSynchronize();

cudaMemcpy(u_arr, d_u, memsize_grid * numGrids, cudaMemcpyDeviceToHost);
cudaMemcpy(du_arr, d_du, memsize_grid * numGrids, cudaMemcpyDeviceToHost);
cudaMemcpy(r_arr, d_r, memsize_grid * numGrids, cudaMemcpyDeviceToHost);

/*
printf("\n\nafter kernel finished\n\n");
for(uint k=0; k<numGrids; ++k)
printf("matrix->du_arr[k*paramH.numNodes + 668]:\t%E\n", du_arr[k*numNodesPerGrid + 668]);//*/

equal = true;
for(int k=1; k<numGrids; ++k)
{
equal &= gridValuesEqual(u_arr, 0, k, "u", numNodesPerGrid);
equal &= gridValuesEqual(du_arr, 0, k, "du", numNodesPerGrid);
equal &= gridValuesEqual(r_arr, 0, k, "r", numNodesPerGrid);
}

if(!equal)
printf("Results are wrong!!\n");
else
printf("All went well!\n");

cudaFree(d_u);
cudaFree(d_du);
cudaFree(d_r);

delete [] u_arr;
delete [] du_arr;
delete [] r_arr;

return 0;
}


生成文件:

CUDA            = 1
DEFINES =

ifeq ($(CUDA), 1)
DEFINES += -DCUDA
CUDAPATH = /usr/local/cuda-6.5
CUDAINCPATH = -I$(CUDAPATH)/include
CUDAARCH = -arch=sm_20
endif

CXX = g++
CXXFLAGS = -pipe -g -std=c++0x -fPIE -O0 $(DEFINES)
VALGRIND = valgrind
VALGRIND_FLAGS = -v --leak-check=yes --log-file=out.memcheck
CUDAMEMCHECK = cuda-memcheck
CUDAMC_FLAGS = --tool memcheck
RACECHECK = $(CUDAMEMCHECK)
RACECHECK_FLAGS = --tool racecheck
INCPATH = -I. $(CUDAINCPATH)
LINK = g++
LFLAGS = -O0
LIBS =

ifeq ($(CUDA), 1)
NVCC = $(CUDAPATH)/bin/nvcc
LIBS += -L$(CUDAPATH)/lib64/
LIBS += -lcuda -lcudart -lcudadevrt
NVCCFLAGS = -g -G -O0 --ptxas-options=-v
NVCCFLAGS += -lcuda -lcudart -lcudadevrt -lineinfo --machine 64 -x cu $(CUDAARCH) $(DEFINES)
endif

all:
$(NVCC) $(NVCCFLAGS) $(INCPATH) -c -o $(DST_DIR)heisenbug.o $(SRC_DIR)heisenbug.cu
$(LINK) $(LFLAGS) -o heisenbug heisenbug.o $(LIBS)

clean:
rm heisenbug.o
rm heisenbug

memrace: all
./heisenbug > out
$(VALGRIND) $(VALGRIND_FLAGS) ./heisenbug > out.memcheck.log
$(CUDAMEMCHECK) $(CUDAMC_FLAGS) ./heisenbug > out.cudamemcheck
$(RACECHECK) $(RACECHECK_FLAGS) ./heisenbug > out.racecheck

最佳答案

请注意,在您撰写本文的全部内容中,我没有看到明确提出的问题,因此我在答复:


我期待着学习这里发生的事情。


您在d_u上存在比赛条件。

根据您自己的陈述:


•为了使块彼此独立,在网格上引入了一个小的重叠部分(尽管只有一个线程正在写入,但每个网格的网格点666、667、668、669是由来自不同块的两个线程读取的)它们,就是发生问题的地方就是这种重叠)


此外,如果您注释掉对d_u的写入,则根据代码中的语句,问题将消失。

CUDA线程块可以按任何顺序执行。您至少有2个不同的块正在从网格点666、667、668、669读取。根据实际发生的情况,结果将有所不同:


两个块都在发生任何写操作之前读取值。
一个块读取该值,然后进行写操作,然后另一个块读取该值。


如果一个块正在读取可被另一块写入的值,则这些块不是彼此独立的(与您的陈述相反)。在这种情况下,块执行的顺序将确定结果,而CUDA未指定块执行的顺序。

请注意,带有cuda-memcheck选项only captures race conditions related to -tool racecheck memory usage__shared__。您发布的内核不使用__shared__内存,因此我不希望cuda-memcheck报告任何内容。

为了收集数据,cuda-memcheck确实会影响块执行的顺序,因此它会影响行为也就不足为奇了。

内核中的printf表示代价高昂的函数调用,它写入全局内存缓冲区。因此,它也会影响执行行为/模式。而且,如果您打印出大量数据,超出了输出的缓冲区行,则在缓冲区溢出的情况下,这样做的代价是非常昂贵的(就执行时间而言)。

顺便说一句,据我所知,Linux Mint是not a supported distro for CUDA。但是,我认为这与您的问题无关;我可以在受支持的配置上重现该行为。

关于cuda - CUDA内核中的Heisenbug,全局内存访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27219649/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com