Cuda-By-Example - 4

引入Thread概念

第4章使用GPU做并行运算的例子,归结起来就是定义一个内核函数,将数组dev_a和dev_b某一对元素相加。GPU发起N个block运行内核函数。每个block有自己的索引,这样kernel就可以凭借这个索引区分自身,来计算数组对应的元素。在这些例子中,每个block只创建了一个线程。

本章开始引入Thread概念,实际上一个block可以创建多个Thread。为了方便说明,前面将两个数组对应元素相加的运算,之后开始用矢量相加来指称。数组a为矢量a的各个分量,数组b为矢量b的各个分量。之前<<<N, 1>>> 的第二个参数一直没有做介绍,现在我们可以猜到,它就是用来指定一个block里创建多少个线程。

既然这样,我们试试创建一个block,block里创建N个线程来完成上一章的矢量相加的例子。main函数中,调用add的方式从

add<<<N, 1>>>(dev_a, dev_b, dev_c);

改为

add<<<1, N>>>(dev_a, dev_b, dev_c);

内核函数add也需要做个很小的修改。之前各个block通过blockIdx.x作为索引,对矢量a,b的分量求和,现在使用同一个block的各个线程来计算各个分量了,索引肯定不能再用blockIdx了。CUDA贴心的送上threadIdx。

int tid = threadIdx.x;

这个代码就只改了这两个地方。

cpp 复制代码
#include "../common/book.h"

#define N   10

__global__ void add( int *a, int *b, int *c ) {
	// int tid = blockIdx.x;
    int tid = threadIdx.x;
    if (tid < N)
        c[tid] = a[tid] + b[tid];
}

int main( void ) {
    int a[N], b[N], c[N];
    int *dev_a, *dev_b, *dev_c;

    // allocate the memory on the GPU
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_b, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_c, N * sizeof(int) ) );

    // fill the arrays 'a' and 'b' on the CPU
    for (int i=0; i<N; i++) {
        a[i] = i;
        b[i] = i * i;
    }

    // copy the arrays 'a' and 'b' to the GPU
    HANDLE_ERROR( cudaMemcpy( dev_a, a, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy( dev_b, b, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );

    // add<<<N,1>>>( dev_a, dev_b, dev_c );
	add<<<1,N>>>( dev_a, dev_b, dev_c );
	

    // copy the array 'c' back from the GPU to the CPU
    HANDLE_ERROR( cudaMemcpy( c, dev_c, N * sizeof(int),
                              cudaMemcpyDeviceToHost ) );

    // display the results
    for (int i=0; i<N; i++) {
        printf( "%d + %d = %d\n", a[i], b[i], c[i] );
    }

    // free the memory allocated on the GPU
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_b ) );
    HANDLE_ERROR( cudaFree( dev_c ) );

    return 0;
}

Block和Thread的限制

受限于硬件资源,GPU能够支持的block数目和每个block里能包含的thread数目都是有限的。书中的说法是Block的最大数目是65535,每个block最多thread数可以通过设备的属性maxThreadsPerBlock查到。2010年最好GPU,这个数字是512。2024年的今天,这个数字也只翻了一倍1024. 无论如何,我们肯定会遇到元素个数大于最大Block数或者maxThreadsPerBlock,甚至两者的乘积65,535 * 512 = 33,553,920。

如何突破这一限制?

首先,我们不能再使用<<<N,1>>>和<<<1,N>>>,而要为block数和每Block里thread数选择合适的值。这里不光有上面所说的原因,还牵涉到CUDA的一个机制,属于同一个block的thread可以共享内存。这个内存跟cudaMalloc分配的内存是有区别的,cudaMalloc分配的内存是从GPU里DDR,甚至是供GPU使用的主板DDR。而给block的共享内存要快得多。后面章节会详细使用方法,这里我们先学习block,thread组合在一起用法。

其次,当元素个数大于限制时,分多次处理。

为了更好的说明这个问题,我们先认识一下block和thread的逻辑结构。
图1:Block和Thread布局

以二维为例(比较好画图,实际上最高支持3维的),通过<<<gridDim, blockDim>>>可以在GPU端生成指定数量的线程,线程以上面的方式进行组合管理。

gridDim指定x,y方向线程块的数量,blockDim指定x,y方向thread的数量。当我们只需要一维来处理,那么y方向设置为1,在图1中对应就是蓝色方框显示的部分。我们用一维来处理矢量相加,二维来处理图像。

那么对于图中Block(1,0)里的Thread(1,0),它来计算矢量的哪个分量呢?在本block中,它前面有1个线程,同时前面还有一个block即block(0,0)里的2个线程,所以它应该处理分量的索引应该是 1 + 1*2 = 3。由此对于任意thread,它要处理的分量的所有计算方法是:

int tid = threadIdx.x + blockIdx.x * blockDim.x

这样,我们解决组合使用block和thread时,索引的计算问题。接下来我们来看,当元素个数超出block/thread组合一次性处理能力时该怎么办。

图1中,一次性只能处理6个元素。超过6个元素,比如说当N=15时,thread(1,0)的任务是计算dev_a[3] + dev_c[3]。最右边的线程thread(2,1)计算dev_a[5] + dev_b[5]。数组再往后,就没有剩余线程可以用了。这时,我们可以让最左边的线程计算完dev_a[0] + dev_b[0]后,来计算dev_a[6]+dev_b[6], 然后再计算dev_a[12] + dev_b[12]。也就是说完成最初计算所得索引的元素的相加之后,跳过 gridDim.x * blockDim.x个元素后, 继续把对应的两个元素相加,直到超出数组大小。

我们的内核函数将修改为:

cpp 复制代码
__global__ void add( int *a, int *b, int *c ) {
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    while (tid < N) {
        c[tid] = a[tid] + b[tid];
        tid += blockDim.x * gridDim.x;
    }
}

长矢量相加

下面是一个长矢量相加的例子,在随书代码的chapter05\add_loop_long_blocks.cu。分量的个数N 为 33*1024。有了前面的经验,看起来应该没有什么难度。要注意是N比较大,所以CPU这边的a,b,c也改成动态分配了。

cpp 复制代码
#include "../common/book.h"

#define N   (33 * 1024)

__global__ void add( int *a, int *b, int *c ) {
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    while (tid < N) {
        c[tid] = a[tid] + b[tid];
        tid += blockDim.x * gridDim.x;
    }
}

int main( void ) {
    int *a, *b, *c;
    int *dev_a, *dev_b, *dev_c;

    // allocate the memory on the CPU
    a = (int*)malloc( N * sizeof(int) );
    b = (int*)malloc( N * sizeof(int) );
    c = (int*)malloc( N * sizeof(int) );

    // allocate the memory on the GPU
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_b, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_c, N * sizeof(int) ) );

    // fill the arrays 'a' and 'b' on the CPU
    for (int i=0; i<N; i++) {
        a[i] = i;
        b[i] = 2 * i;
    }

    // copy the arrays 'a' and 'b' to the GPU
    HANDLE_ERROR( cudaMemcpy( dev_a, a, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy( dev_b, b, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );

    add<<<128,128>>>( dev_a, dev_b, dev_c );

    // copy the array 'c' back from the GPU to the CPU
    HANDLE_ERROR( cudaMemcpy( c, dev_c, N * sizeof(int),
                              cudaMemcpyDeviceToHost ) );

    // verify that the GPU did the work we requested
    bool success = true;
    for (int i=0; i<N; i++) {
        if ((a[i] + b[i]) != c[i]) {
            printf( "Error:  %d + %d != %d\n", a[i], b[i], c[i] );
            success = false;
        }
    }
    if (success)    printf( "We did it!\n" );

    // free the memory we allocated on the GPU
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_b ) );
    HANDLE_ERROR( cudaFree( dev_c ) );

    // free the memory we allocated on the CPU
    free( a );
    free( b );
    free( c );

    return 0;
}
相关推荐
fpcc4 天前
并行编程实战——CUDA编程的流的优先级
c++·cuda
碧海潮生_CC5 天前
【CUDA笔记】03 CUDA GPU 架构与一般的程序优化思路(下)
笔记·架构·cuda
中医正骨葛大夫6 天前
一文解决如何在Pycharm中创建cuda深度学习环境?
pytorch·深度学习·pycharm·软件安装·cuda·anaconda·配置环境
lvxiangyu1111 天前
wsl2 ubuntu24 opengl 无法使用nvidia显卡 解决方法记录
wsl·cuda·opengl
李昊哲小课11 天前
wsl ubuntu24.04 cuda13 cudnn9 pytorch 显卡加速
人工智能·pytorch·python·cuda·cudnn
wanzhong233313 天前
CUDA学习2-CPU和GPU的性能优化
深度学习·gpu·cuda·高性能计算
碧海潮生_CC18 天前
【CUDA笔记】01-入门简介
笔记·cuda
喆星时瑜21 天前
关于 ComfyUI 的 Windows 本地部署系统环境教程(详细讲解Windows 10/11、NVIDIA GPU、Python、PyTorch环境等)
python·cuda·comfyui
安全二次方security²25 天前
CUDA C++编程指南(1)——简介
nvidia·cuda·c/c++·device·cuda编程·architecture·compute unified
千年奇葩1 个月前
Unity性能优化之:利用CUDA加速Unity实现大规模并行计算。从环境搭建到实战案例
c++·人工智能·unity·游戏引擎·cuda