CUDA编程

一、cuda/gpu线程模型

1、cuda线程模型

        核函数调用方法如下:

                xxx_xxx_xxx<<<grid_size, block_size>>>(函数参数);

        以上核函数调用会在gpu中创建grid_size * block_size个线程并行执行核函数内容

  • 一维时:grid_size为一维时最大值,block_size最大值1024;
  • 三维时:grid_size.x/grid_size.y/grid_size.z最大值/65535/65535,block_size.x/block_size.y/block_size.z最大值1024/1024/64,且block_size.x*block_size.y*block_size.z不能大于1024。

2、GPU线程模型

3、cuda线程模型与GPU线程模型对应关系

    二、cuda/gpu内存模型


    1、PageableMemory(HOST分页内存,位于HOST DDR):

            1、主机使用malloc()/free();
            2、主机使用,GPU不能使用;
            3、在stream中如果使用了异步数据传输,不能使用该类型的Memory。

    2、PinnedMemory(固定内存,位于HOST DDR):

            1、使用cudaMallocHost()/cudaHostAlloc/cudaFreeHost();
            2、GPU可通过DMA访问的Host Memory;
            3、在stream中如果使用了异步数据传输,需要使用该类型的Memory。

    3、GlobalMemory(全局内存,位于GPU DDR):

            1、全局内存:cudaMallc()/cudaFree();
            2、静态全局内存变量:核函数外__device__;
            3、常量内存:核函数外__constant__;
            4、纹理内存/表面内存;
            5、局部内存:在核函数内定义的不加任何修定符的变量在寄存器内存装不下时。
            6、全局内存的合并传输?

    4、SharedMemory(共享内存,位于GPU SM SRAM):

            1、类似于寄存器内存,区别是整个线程块可见;
            2、静态共享内存使用__shared__修饰,声明时需要指定大小;
            3、动态共享内存使用extern __shared__修饰,声明时不能指定大小,在调用核函数时指定每个线程使用的共享内存大小:
                    xxx_xxx_xxx<<<grid_size, block_size, SharedMemorySize>>>(函数参数);
            4、共享内存因为更靠近计算单元,所以访问速度更快;
            5、共享内存通常可以作为访问全局内存的缓存使用;
            6、可以利用共享内存实现线程间的通信;
            7、通常与__syncthreads同时出现,这个函数是同步block内的所有线程,全部执行到这一行才往下走;
            8、使用方式,通常是在线程id为0的时候从global memory取值,然后syncthreads,然后再使用。

            9、共享内存bank冲突问题?

    5、RegisterMemory(寄存器内存,位于GPU SM):

            1、在核函数内定义的不加任何修定符的变量。

    三、cuda runtime api

            以cuda为前缀:CUDA Runtime API

    四、cuda driver api

            以cu为前缀:CUDA Driver API

    五、原子函数

            atomicAdd、atomicSub、atomicExch、atomicCAS、atomicInc、atomicDec、atomicMax、atomicMin、atomicAnd、atomicOr、atomicXor

    六、线程束

    • SM按照线程束(包括32个线程)进行运算,一个线程块只能调度到一个SM,如一个线程块有128个线程,那就是4个线程束,这时会被调度到一个SM上,这个SM多度执行这4个线程束,一个SM最多可以驻留32个线程束,即最多32*32=1024个线程;
    • 一个SM可以运行多个线程块,如线程块有16个线程,那么一个SM可以同时运行两个线程块。

    1、线程束内基本函数

    • unsigned __ballot_sync(unsigned mask, int predicate);
    • int __all_sync(unsigned mask, int predicate);
    • int __any_sync(unsigned mask, int preducate);
    • T __shfl_sync(unsigned mask, T v, int srcLane, int w = warpSize);
    • T __shfl_up_sync(unsigned mask, T v, unsigned d, int w = warpSize);
    • T __shfl_down_sync(unsigned mask, T v, unsigned d, int w = warpSize);
    • T __shfl_xor_sync(unsigned mask, T v, int laneMask, int w = warpSize);
    • __syncwarp();
    • reduce_shfl();

    2、协作组-线程块片函数

    • unsigned __ballot_sync(int predicate);
    • int __all_sync(int predicate);
    • int __any_sync(int predicate);
    • T __shfl_sync(T v, int srcLane);
    • T __shfl_up_sync(T v, unsigned d);
    • T __shfl_down_sync(T v, unsigned d);
    • T __shfl_xor_sync(T v, int laneMask);

    七、常用cuda库

    • Thrust:类似于C++的标准模板库(standard template library)
    • cuBLAS:基本线性代数子函数(basic liner algebra subroutines)
    • cuFFT:快速傅里叶变换(fast Fourier transforms)
    • cuSPARSE:稀疏(sparse)矩阵
    • cuRAND:随机数生成器(rendom number generator)
    • cuSolver:稠密(dense)矩阵和稀疏矩阵计算库
    • cuDNN:深度神经网络(deep neural networks)

    八、context、stream、event

            流是一种基于context之上的任务管道抽象,未显式调用函数创建stream时使用默认stream:default stream,在一个stream里的执行步骤都是顺序的,可以是阻塞也可以是非阻塞,如下示例:

    cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice);
    cudaMemcpy(d_y, h_y, M, cudaMemcpyHostToDevice);
    
    sum<<<grid_size, block_size>>>(d_x, d_y, d_z, N);
    
    cudaMemcpy(h_z, d_z, cudaMemcpyDeviceToHost);

            stream并行实现计算与通信重叠,通过创建stream可以同时运行多个CUDA stream,多个stream可实现核函数和通信的并行执行,stream的用法如下:

    1、创建stream
    cudaError_t cudaStreamCreate(cudaStream_t *);    // 创建stream
    cudaError_t cudaStreamDestroy(cudaStream_t);     // 销毁stream
    
    2、使用stream
    xxx_kernel<<<grid_size, block_size>>>(函数参数):
    xxx_kernel<<<grid_size, block_size, SharedMemorySize>>>(函数参数):
    xxx_kernel<<<grid_size, block_size, SharedMemorySize, stream_id>>>(函数参数):
    
    3、等待完成
    cudaError_t cudaStreamSynchronize(cudaStream_t stream);    // 阻塞等待完成
    cudaError_t cudaStreamQuery(cudaStream_t stream);          // 非阻塞检查完成状态
    
    一个完成的示例如下:
    int main(){
     
        int device_id = 0;
        checkRuntime(cudaSetDevice(device_id));
     
        cudaStream_t stream = nullptr;
        checkRuntime(cudaStreamCreate(&stream)); //创建一个流
     
        // 在GPU上开辟空间
        float* memory_device = nullptr;
        checkRuntime(cudaMalloc(&memory_device, 100 * sizeof(float)));
     
        // 在CPU上开辟空间并且放数据进去,将数据复制到GPU
        float* memory_host = new float[100];
        memory_host[2] = 520.25;
            // 异步复制操作,主线程不需要等待复制结束才继续
        checkRuntime(cudaMemcpyAsync(memory_device, memory_host, sizeof(float) * 100, cudaMemcpyHostToDevice, stream)); 
     
        // 在CPU上开辟pin memory,并将GPU上的数据复制回来 
        float* memory_page_locked = nullptr;
        checkRuntime(cudaMallocHost(&memory_page_locked, 100 * sizeof(float)));
        checkRuntime(cudaMemcpyAsync(memory_page_locked, memory_device, sizeof(float) * 100, cudaMemcpyDeviceToHost, stream)); // 异步复制操作,主线程不需要等待复制结束才继续
        printf("%f\n", memory_page_locked[2]);//结果是0,因为还没等待结果返回
        checkRuntime(cudaStreamSynchronize(stream)); //统一等待流队列中的结果
        
        printf("%f\n", memory_page_locked[2]);
        
        // 释放内存
        checkRuntime(cudaFreeHost(memory_page_locked));
        checkRuntime(cudaFree(memory_device));
        checkRuntime(cudaStreamDestroy(stream));
        delete [] memory_host;
        return 0;
    }

            event用以监控stream是否到达了某个检查点,可实现多个stream同步,举例说明如下:

    • 在stream 1上面执行kernel 1 和 kernel 3
    • 在stream 2上面执行kernel 2,但是必须等到stream 1上面的kernel 1执行结束之后才能开始
    // Create streams and event
    cudaStream_t stream1, stream2;
    cudaEvent_t event1;
    cudaEventCreate(&event1);
    
    // Execute kernel1 in stream1
    kernel1<<<gridDim, blockDim, 0, stream1>>>(d_data1, repeat);
    cudaEventRecord(event1, stream1); // Record event1 after kernel1 execution in stream1
    
    // Execute kernel2 in stream2, waiting for event1
    cudaStreamWaitEvent(stream2, event1, 0);
    
    kernel2<<<gridDim, blockDim, 0, stream2>>>(d_data1, repeat);
    
    // Execute kernel3 in stream1 on a different array
    kernel3<<<gridDim, blockDim, 0, stream1>>>(d_data2, repeat);
    
    // Synchronize streams
    cudaStreamSynchronize(stream1);
    cudaStreamSynchronize(stream2);
    
    // Free device memory and destroy streams and event
    cudaStreamDestroy(stream1);
    cudaStreamDestroy(stream2);
    cudaEventDestroy(event1);

    九、统一内存

            1、动态统一内存在HOST端使用cudaMallocManaged()函数进行申请,使用cudaFree()函数释放,HOST即Device端使用时,无需进行Memory Copy而直接使用,如下示例:

    int main(void)
    {
    	const int N = 100000000;
    	const int M = sizeof(double) * N;
    	double *x, *y, *z;
    	CHECK(cudaMallocManaged((void **)&x, M));
    	CHECK(cudaMallocManaged((void **)&y, M));
    	CHECK(cudaMallocManaged((void **)&z, M));
    
    	for (int n = 0; n < N; ++n)
    	{
    		x[n] = a;
    		y[n] = b;
    	}
    
    	const int block_size = 128;
    	const int grid_size = N / block_size;
    	add<<<grid_size, block_size>>>(x, y, z);
    
    	CHECK(cudaDeviceSynchronize());
    	check(z, N);
    
    	CHECK(cudaFree(x));
    	CHECK(cudaFree(x));
    	CHECK(cudaFree(x));
    
    	return 0
    }

            2、静态统一内存使用__device__ __managed__进行修饰,如下示例:

    #include <stdio.h>
    
    __device__ __managed__ int ret[1000];
    
    __globle__ void AplusB(int a, int b)
    {
    	ret[threadIdx.x] = a + b + threadIdx.x;
    }
    
    int main(void)
    {
    	AplusB<<<1, 1000>>>(10, 100);
    	CHECK(cudaDeviceSynchronize());
    	for (int i = 0; i < 1000; i++)
    	{
    		printf("%d: A+B = %d\n", i, ret[i]);
    	}
    
    	return 0
    }

            3、统一内存可以申请超出实际内存的容量,因为实际使用时才会涉及到物理内存,类似缺页异常,申请的统一内存可以使用gpu_touch()核函数进行全部内存的初始化,如果申请了超量的统一内存,使用gpu_touch()核函数进行初始化时,在超量的部分Memory会初始化失败;

            4、统一内存如果涉及到HOST Memory+Device Memory,如果Device不访问这段统一内存,那么HOST只能访问到HOST部分的内存,及时让Device使用cudaMemAdvise()/cudaMemPreftchAsync()函数同步一下统一内存,HOST才能访问到全部的统一内存,参考代码如下:

    int main(void)
    {
    	int device_id = 0;
    	CHECK(cudaGetDevice(&device_id));
    
    	const int N = 100000000;
    	const int M = sizeof(double) * N;
    	double *x, *y, *z;
    	CHECK(cudaMallocManaged((void **)&x, M));
    	CHECK(cudaMallocManaged((void **)&y, M));
    	CHECK(cudaMallocManaged((void **)&z, M));
    
    	for (int n = 0; n < N; ++n)
    	{
    		x[n] = a;
    		y[n] = b;
    	}
    
    	const int block_size = 128;
    	const int grid_size = N / block_size;
    
    	CHECK(cudaMemPrefetchAsync(x, M, device_id, NULL));
    	CHECK(cudaMemPrefetchAsync(y, M, device_id, NULL));
    	CHECK(cudaMemPrefetchAsync(z, M, device_id, NULL));
    
    	add<<<grid_size, block_size>>>(x, y, z);
    
    	CHECK(cudaFree(x));
    	CHECK(cudaMemPrefetchAsync(z, M, cudaCpuDeviceId, NULL));
    	CHECK(cudaDeviceSynchronize());
    	check(z, N);
    
    	CHECK(cudaFree(x));
    	CHECK(cudaFree(y));
    	CHECK(cudaFree(z));
    
    	return 0;
    }

    十、疑问

    1、核函数怎么被加载到gpu,gpu怎么运行核函数?
    2、runtime/driver api怎么与KMD交互?
    3、卷积、激活、池化、归一化、标量、向量、矩阵、张量?


    GPU软件抽象与硬件映射的理解(Grid、Block、Warp、Thread与SM、SP)
    GPU架构
    GPU架构与通信互联技术介绍

    CUDA运行API:RuntimeAPI
    NVIDA CUDA-DirverAPI入门
    具体化,讲解cuda runtime api是什么,runtime driver是什么
    【YOLO系列】YOLOv5超详细解读(源码详解+入门实践+改进)

    一文读懂cuda stream与cuda event

    一文说清楚,DeePSeek用的PTX与CUDA的区别
    自定义CUDA实现PyTorch算子的四种简单方法
    PhD 第四学期 年中随笔
    矩阵分解之: 特征值分解(EVD)、奇异值分解(SVD)、SVD++
    cuda runtime/driver API解析
    【CUDA】Driver API 和 Runtime API
    https://www.zhihu.com/people/li-wei-27-70-68/posts

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值