[英]Upload data in shared memory for convolution kernel
我在参考评论中理解批量加载时遇到一些困难。 为了计算像素中的卷积,大小为5的掩模必须以该特定像素为中心。 图像被分为图块。 应用卷积掩模后的这些图块是最终输出图块,其大小为TILE_WIDTH*TILE_WIDTH
。 对于属于输出图块边框的像素,当此图块属于图像的边框时,图像必须从相邻图块借用一些像素。 否则,这些借来的值被赋值为零。 这两个步骤描述于
if (srcY >= 0 && srcY < height && srcX >= 0 && srcX < width)
N_ds[destY][destX] = I[src];
else
N_ds[destY][destX] = 0;
因此,共享内存阵列的TILE_WIDTH + Mask_width - 1
维度。 我不清楚代码的以下部分。
destY
和destX
索引。 将输出索引除以输入切片宽度意味着什么? srcY
添加srcX
索引。 为什么destY
和destX
索引参与srcY
添加srcX
索引?
srcY = blockIdx.y * TILE_WIDTH + destY - Mask_radius;
srcX = blockIdx.x * TILE_WIDTH + destX - Mask_radius;
TILE_WIDTH * TILE_WIDTH
? 编辑:图片添加。 在绿色中有输出瓦片,在红色中我们有掩模以114索引为中心。 很明显,面具借用了不同瓷砖的元素。 最后,该图像指的是一个通道。
示例:根据下图,我试图写一个例子。 输出块具有blockIdx.x=1
和blockIdx.y=1
基于destY=0
和destX=0
。 此外, srcY = 1*6+0-3=3
, srcX = 3
且src = (3*18+3)*3+0=171
。 根据计算和图像示例,我们没有匹配。 在第一个共享内存位置中,应存储的值是全局索引57
。 上述计算有什么问题? 有人可以帮忙吗?
#define Mask_width 5
#define Mask_radius Mask_width/2
#define TILE_WIDTH 16
#define w (TILE_WIDTH + Mask_width - 1)
#define clamp(x) (min(max((x), 0.0), 1.0))
__global__ void convolution(float *I, const float* __restrict__ M, float *P,
int channels, int width, int height) {
__shared__ float N_ds[w][w];
int k;
for (k = 0; k < channels; k++) {
// First batch loading
int dest = threadIdx.y * TILE_WIDTH + threadIdx.x,
destY = dest / w, destX = dest % w,
srcY = blockIdx.y * TILE_WIDTH + destY - Mask_radius,
srcX = blockIdx.x * TILE_WIDTH + destX - Mask_radius,
src = (srcY * width + srcX) * channels + k;
if (srcY >= 0 && srcY < height && srcX >= 0 && srcX < width)
N_ds[destY][destX] = I[src];
else
N_ds[destY][destX] = 0;
// Second batch loading
dest = threadIdx.y * TILE_WIDTH + threadIdx.x + TILE_WIDTH * TILE_WIDTH;
destY = dest / w, destX = dest % w;
srcY = blockIdx.y * TILE_WIDTH + destY - Mask_radius;
srcX = blockIdx.x * TILE_WIDTH + destX - Mask_radius;
src = (srcY * width + srcX) * channels + k;
if (destY < w) {
if (srcY >= 0 && srcY < height && srcX >= 0 && srcX < width)
N_ds[destY][destX] = I[src];
else
N_ds[destY][destX] = 0;
}
__syncthreads();
float accum = 0;
int y, x;
for (y = 0; y < Mask_width; y++)
for (x = 0; x < Mask_width; x++)
accum += N_ds[threadIdx.y + y][threadIdx.x + x] * M[y * Mask_width + x];
y = blockIdx.y * TILE_WIDTH + threadIdx.y;
x = blockIdx.x * TILE_WIDTH + threadIdx.x;
if (y < height && x < width)
P[(y * width + x) * channels + k] = clamp(accum);
__syncthreads();
}
}
您的问题在概念上类似于我在StackOverflow上的第一个问题: 通过BS_X BS_Y线程移动(BS_X + 1) (BS_Y + 1)全局存储矩阵 。
您TILE_WIDTHxTILE_WIDTH
以下问题: 每个大小为TILE_WIDTHxTILE_WIDTH
线程块应填充大小的共享内存区域(TILE_WIDTH + Mask_width - 1)x(TILE_WIDTH + Mask_width - 1)
。
4)一般来说,有两个载荷的直观解释是什么?
由于共享内存区域(TILE_WIDTH + Mask_width - 1)x(TILE_WIDTH + Mask_width - 1)
大于块大小TILE_WIDTHxTILE_WIDTH
并假设它小于2xTILE_WIDTHxTILE_WIDTH
,因此每个线程最多应将两个元素从全局内存移动到共享内存。 这就是为什么你有一个两阶段加载的原因。
1)
destY
和destX
索引。 将输出索引除以输入切片宽度意味着什么?
这涉及第一个加载阶段,它被指定从全局内存加载TILE_WIDTHxTILE_WIDTH
元素并填充共享内存区域的最上部分。
所以,操作
dest = threadIdx.y * TILE_WIDTH + threadIdx.x;
平坦化通用线程的2D坐标
destX = dest % w;
destY = dest / w;
进行逆运算,因为它计算通用线程相对于共享存储区的2D坐标。
2)
srcY
添加srcX
索引。 为什么destY
和destX
索引参与srcY
添加srcX
索引?
srcY = blockIdx.y * TILE_WIDTH + destY - Mask_radius;
srcX = blockIdx.x * TILE_WIDTH + destX - Mask_radius;
(blockIdx.x * TILE_WIDTH, blockIdx.y * TILE_WIDTH)
如果块大小和共享内存大小相同,则为全局内存位置的坐标。 由于你也是从neighboor tile中“借用”内存值,你必须将上面的坐标移动(destX - Mask_radius, destY - Mask_radius)
。
3)为什么在第二次加载时我们使用偏移量TILE_WIDTH * TILE_WIDTH?
您有这个偏移量,因为在第一个内存阶段,您已经填充了共享内存的“第一个” TILE_WIDTHxTILE_WIDTH
位置。
编辑
下图说明了展平线程索引dest
与共享内存位置之间的对应关系。 在图片中,蓝色框表示通用图块的元素,而红色框表示邻居图块的元素。 蓝色和红色框的并集对应于整个共享内存位置。 如您所见,线程块的所有256
个线程都参与填充绿线上方的共享内存的上部,而只有145
个线程涉及填充绿线下方的共享内存的下部。 现在您还应该了解TILE_WIDTH x TILE_WIDTH
偏移量。
请注意,由于特定的参数选择,每个线程最多有2
内存负载。 例如,如果你有TILE_WIDTH = 8
,那么线程块中的线程数是64
,而共享内存大小是12x12=144
,这意味着每个线程负责执行至少 2
共享内存写入144/64=2.25
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.