CUDA矩阵转置(共享内存 tile)

article/2023/6/4 16:19:13

Udacity的CUDA编程课程中介绍了CUDA实现矩阵转置的六种方式,本文介绍其中的一种方式

如果矩阵为N*N的方阵。该方式让每个线程处理一个矩阵元素,总共需要N*N个线程。首先,声明两个常量并配置blocks,threads:

const int N=1024;
const int K=32;
dim3 blocks(N/K,N/K); 
dim3 threads(K,K);	

内核函数:

__global__ void 
transpose_parallel_per_element_tiled(float in[], float out[])
{// (i,j) locations of the tile corners for input & output matrices:int in_corner_i  = blockIdx.x * blockDim.x, in_corner_j  = blockIdx.y * blockDim.y;int out_corner_i = in_corner_j, out_corner_j = in_corner_i;int x = threadIdx.x, y = threadIdx.y;__shared__ float tile[K][K];// coalesced read from global mem, TRANSPOSED write into shared mem:tile[y][x] = in[(in_corner_i + x) + (in_corner_j + y)*N];__syncthreads();// read from shared mem, coalesced write to global mem:out[(out_corner_i + x) + (out_corner_j + y)*N] = tile[x][y];
}

内核涉及两个输入参数,in代表输入矩阵,out代表in的转置矩阵。为了更好地理解这段代码,我们将矩阵规模缩小并画图展示。假设N=4,K=2。blocks(2,2),threads(2,2)。

如图所示,矩阵被分成了4个区域,每个区域包含2*2=4个数据,分别由4个线程块进行处理。

矩阵in左下那个方形区域S由block(0,1)处理。该线程块corner(ici,icj)(threads(0,0))所处理数据在in矩阵中的坐标为(ici,icj)(0,2)。S区域的其他数据坐标可由[(icj+y)*N+ici+x]得到,x,y是线程索引。blocks,threads的二维索引值x,y分别代表了矩阵的水平方向和竖直方向。

内核声明了一个和数据块大小相同的共享内存tile,用于存储S区域的数据。block读取S区域数据并按原样复制到tile当中,一个线程复制一个数据。随后block将tile中的数据转置,再把经过转置的tile中的数据写入到out矩阵当中S'区域。根据转置行列互换的性质可知,block(0,1) corner所处理数据在out矩阵中的坐标为(oci=icj,ocj=ici)即(2,0),S'区域的其他数据索引可由[(ocj+y)*N+oci+x]得到。转置后,in矩阵S区域的数据会被block(0,1)转置到out矩阵S'。tile转置和写入数据到out是由最后一行代码完成的。

该例中,数据读取和写入都利用了全局内存合并访问,这也说明,对于二维矩阵块(x,y),CUDA按x优先顺序处理线程。


出于某种目的,我需要线程块的尺寸为非方形。假设blocks(4,2),threads(1,2)。内核函数__shared__ float tile[K][K];需要修改为__shared__ float tile[2][1]; 这样修改之后,问题在于无法在tile中对数据进行转置。所以我在将数据写入到out的时候,将位置索引[(oci + x) + (ocj + y)*N]转置为(oci + y) + (ocj + x)*N,实现非方矩阵块的矩阵转置。

这段解释不要看了。内核函数中的in_corner_i,in_corner_j分别代表线程块左上角线程(也就是threads(0,0))在矩阵中的x索引(水平方向)和y索引(竖直方向),作为线程块内部线程索引数据的参照点(线程块(0,0)线程所处理数据在矩阵中的位置)。对于block(0,1),in_corner_i=0,in_corner_j=2。转置后,S区域内的元素会被转置到右上方区域S’。所以block(0,1)的功能就是把in矩阵S区域的数据转置并复制到out矩阵S'区域。这就需要block(0,1)找到out矩阵右上方区域的参照点索引。根据转置行列互换的性质,得到out_corner_i(x索引)=in_corner_j=2,out_corner_j(y索引)=in_corner_i=0。x,y变量分别是线程在线程块中的二维索引。

共享内存tile的尺寸和block一样,block读取in数据并按原样复制到tile当中,一个线程复制一个数据。同步后,再把tile中的数据按转置顺序写入到out矩阵当中的对应位置。转置的过程相当于在共享内存中完成,所以线程读取和写入都用[(in_corner_i + x) + (in_corner_j + y)*N]来索引数据。

 

 

 

 

 

 

 


https://www.dgrt.cn/a/2089199.html

相关文章

Visual Studio Code 配置java开发环境

最近在学习算法,有时需要在自己的机器上调试一下代码。有些算法题目的题解是用java编的,因为这类代码只是单个的java文件,所以不想动用MyEclipse那样的重型工具。正好机器上有一个轻量级的VS Code,我就试着在上面搭了一个java开发…

nyoj97兄弟郊游问题

时间限制:3000 ms | 内存限制:65535 KB难度:2描述兄弟俩骑车郊游,弟弟先出发,每分钟X米,M分钟后,哥哥带一条狗出发。以每分钟Y米的速度去追弟弟,而狗则以每分钟Z米的速度向弟弟跑去…

isPrime 判断素数的函数

c语言中int isPrime(int n)是什么意思 isPrime 是自定义的一个函数&#xff0c;传入一个整数n&#xff0c;判断是否为素数。若是返回1&#xff0c;否则返回0。 #include "stdio.h"int isprime(int a) //判断素数的函数{int j;for(j2;j<a;j)if(a%j0) //如果有因数…

isPrime 判断素数的函数

c语言中int isPrime(int n)是什么意思 isPrime 是自定义的一个函数&#xff0c;传入一个整数n&#xff0c;判断是否为素数。若是返回1&#xff0c;否则返回0。 #include "stdio.h" int isprime(int a) //判断素数的函数 {int j;for(j2;j<a;j)if(a%j0) //如果有因…

输入四个整数,找出其中的最大值,用函数的嵌套调用来处理

输入四个整数&#xff0c;找出其中的最大值&#xff0c;用函数的嵌套调用来处理 #include<stdio.h> int main() {int max4(int a,int b,int c,int d);int a,b,c,d,max;scanf("%d%d%d%d",&a,&b,&c,&d);maxmax4(a,b,c,d);printf("max%d\n&q…

Fibonacci Again!

Fibonacci Again! 时间限制: 1 Sec 内存限制: 128 MB题目描述 求第n个斐波那契数是否是一个素数,n为整数f[n]f[n-1]f[n-2] (2<n<30)f[1]3,f[2]7输入 输入整数m,0<m<30,输入-1表示结束输入输出 如果f[m]是素数 则输出Yes,否则输出No,每行输出占一行。样例输入 23-1…

XYNU OJ 1073: 习题5-3-2 求最大公约数

题目描述 输入两个正整数&#xff0c;求其最大公约数。输入 测试数据有多组&#xff0c;每组输入两个正整数&#xff0c;两个正整数之间以空格分隔。输出 对于每组输入,输出其最大公约数。 每组对应一个输出&#xff0c;单独占一行。 样例输入 14 4921 66样例输出 73#include&l…

nyoj218 Dinner

Dinner 时间限制&#xff1a;100 ms | 内存限制&#xff1a;65535 KB描述Little A is one member of ACM team. He had just won the gold in World Final. To celebrate, he decided to invite all to have one meal. As bowl, knife and other tableware is not enough in …