CUDA线程如何工作

时间:2014-02-12 07:39:47

标签: cuda

我对线程的形成和执行方式有很多怀疑。

首先,文档将GPU线程描述为轻量级线程。 假设我希望将两个100*100矩阵相乘。如果每个元素都由不同的线程计算,则需要100*100个线程。但是,我的GPU(NVIDIA GT 640M LE)规格显示了两个SM,每个SM只能支持2048个线程。考虑到我的GPU不能支持如此多的线程,如何计算其余的并行元素是两种可能。

还要考虑基本的矢量添加代码。假设我调用一个包含1个块和64个线程的内核来添加两个100个元素的数组,如下所示:

    __global__ void add(int* a,int* b,int* c)
    {
        int i = threadIdx.x;
        for(i<100)
        {
            c[i] = a[i] + b[i];
        {    
     }

由于只初始化了64个线程,我假设并行添加了64个元素。

  • 如何添加其余元素?
  • warp调度程序如何决定分配哪些线程来添加最后36个元素?

我的主要问题是:

我不明白线程如何知道要操作哪些元素。

3 个答案:

答案 0 :(得分:6)

您的卡具有计算功能3.0,请参阅here

从“CUDA C编程指南”的表12中,您提到的计算功能的2048个线程数是指每个多处理器的最大驻留线程数。这并不意味着您无法在整体上启动超过2048个线程。例如,从该表的上面几行中,您可以读到线程块网格的最大最大x维度为2^31-1。这意味着启动它是完全合法的,例如,1d线程网格,例如8192个线程。原因是该卡将执行线程扭曲之间的上下文切换,如本文所示:What is the context switching mechanism in GPU?

关于问题的第二部分,add函数的实现在概念上是错误的。您使用索引i作为线程索引和for循环索引。更正确的实现是以下

__global__ void add(int* a,int* b,int* c)
{
    int i = threadIdx.x;
    c[i] = a[i] + b[i];
}

上述写法意味着以下内容:每个线程将执行两个赋值,即

    int i = threadIdx.x;
    c[i] = a[i] + b[i];

现在,例如,对于线程#3threadIdx.x变量的值将为3。因此,线程#3将处理对其内存空间私有的局部变量i,其值将分配给3。此外,它将从全局内存加载a[3]b[3],将它们相加,将结果分配给c[3],然后将最终结果存储到全局内存中。因此,当您启动网格时,您当然不能仅通过100个线程填充整个64元素数组,并且您将需要100个线程。

请注意,上述说明过于简单。我建议你阅读一些基本的教科书作为着名的CUDA示例。

答案 1 :(得分:1)

将为您介绍CUDA中的4 * 4矩阵添加程序。它可能会让您了解如何启动和运行线程。

int main()
    {
     int *a, *b, *c;            //To store your matrix A & B in RAM. Result will be stored in matrix C
     int *ad, *bd, *cd;         // To store matrices into GPU's RAM. 
     int N =16;   

          //No of rows and columns.

 size_t size=sizeof(float)* N * N;

 a=(float*)malloc(size);     //Allocate space of RAM for matrix A
 b=(float*)malloc(size);     //Allocate space of RAM for matrix B

//allocate memory on device
  cudaMalloc(&ad,size);
  cudaMalloc(&bd,size);
  cudaMalloc(&cd,size);

//initialize host memory with its own indices
    for(i=0;i<N;i++)
      {
    for(j=0;j<N;j++)
         {
            a[i * N + j]=(float)(i * N + j);
            b[i * N + j]= -(float)(i * N + j);
         }
      }

//copy data from host memory to device memory
     cudaMemcpy(ad, a, size, cudaMemcpyHostToDevice);
     cudaMemcpy(bd, b, size, cudaMemcpyHostToDevice);

//calculate execution configuration 
   dim3 grid (1, 1, 1); 
   dim3 block (16, 1, 1);

//each block contains N * N threads, each thread calculates 1 data element

    add_matrices<<<grid, block>>>(ad, bd, cd, N);

   cudaMemcpy(c,cd,size,cudaMemcpyDeviceToHost);  
   printf("Matrix A was---\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",a[i*N+j]);
        printf("\n");
    }

   printf("\nMatrix B was---\n");
   for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",b[i*N+j]);
        printf("\n");
    }

    printf("\nAddition of A and B gives C----\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",c[i*N+j]);   //if correctly evaluated, all values will be 0
        printf("\n");
    }



    //deallocate host and device memories
    cudaFree(ad); 
    cudaFree(bd); 
    cudaFree (cd);

    free(a);
    free(b);
    free(c);

    getch();
    return 1;
}

/////Kernel Part

__global__ void add_matrices(float *ad,float *bd,float *cd,int N)
{
  int index;
  index = blockIDx.x * blockDim.x + threadIDx.x            

  cd[index] = ad[index] + bd[index];
}

让我们举一个16 * 16矩阵的例子。  你有两个矩阵A和B,尺寸为16 * 16 ..

首先,你必须决定你的线程配置。 您可以启动一个内核函数,它将执行矩阵加法的并行计算,这将在您的GPU设备上执行。

现在,使用一个内核函数启动一个网格。 网格最多可以有65,535个块,这些块可以以三维方式排列。 (65535 * 65535 * 65535)。

网格中的每个块最多可以有1024个线程。这些线程也可以以三维方式排列(1024 * 1024 * 64)

现在我们的问题是添加16 * 16矩阵..

A | 1  2  3  4 |        B | 1  2  3  4 |      C| 1  2  3  4 |
  | 5  6  7  8 |   +      | 5  6  7  8 |   =   | 5  6  7  8 | 
  | 9 10 11 12 |          | 9 10 11 12 |       | 9 10 11 12 |  
  | 13 14 15 16|          | 13 14 15 16|       | 13 14 15 16|

我们需要16个线程来执行计算。

i.e. A(1,1) + B (1,1) = C(1,1)
     A(1,2) + B (1,2) = C(1,2) 
     .        .          .
     .        .          . 
     A(4,4) + B (4,4) = C(4,4) 

所有这些线程将同时执行。 所以我们需要一个包含16个线程的块。 为方便起见,我们将以(16 * 1 * 1)方式在一个块中安排线程 因为没有线程是16所以我们只需要一个块来存储这16个线程。

因此,网格配置将为dim3 Grid(1,1,1),即网格只有一个块 和块配置将是dim3 block(16,1,1),即块将有16个按列排列的线程。

以下程序将为您提供有关其执行的清晰概念。 理解索引部分(即threadIDs,blockDim,blockID)是重要的部分。你需要通过CUDA文献。一旦你对索引有了清晰的认识,你将赢得一半的战斗。所以花一些时间与cuda书籍......: - )

答案 2 :(得分:0)

for在这里是非常错误的 - 一些线程与threadid&lt; 100将运行forewer。 对于新手来说,它可以用这种方式解释:threadid是由系统值预定义的,它显示当前的线程号。当前线程从a中获取它的值,从b中将其写入c,因此它将是

int i = threadIdx.x;
c[i] = a[i] + b[i];

如果您的数组大小为100,则与64x的块大小不匹配, 为了使某些线程不读取/写出界限,请执行:

int i = threadIdx.x;
    if(i < 100){

        c[i] = a[i] + b[i];
    }

你只会在最后一个街区出现分歧。 可能你想要那个