CUDA优化

CUDA优化

image

避免Warp分化

if (con)
{
    //do something
}
else
{
    //do something
}

假设这段代码是核函数的一部分,那么当一个线程束的32个线程执行这段代码的时候,如果其中16个执行if中的代码段,而另外16个执行else中的代码块,同一个线程束中的线程,执行不同的指令,这叫做线程束的分化。

解决bank conflict

没4个字节一个bank,一共有32个bank,然后每个bank有多个字段

在共享内存中,就像电影院的座位一样:一列的座位就相当于一个bank,所以每行有32个座位,在每个座位上可以“坐”一个32-bits的数据(或者多个小于32-bits的数据,如4个char型的数据,2个short型的数据);而正常情况下,我们是按照先坐完一行再坐下一行的顺序来坐座位的,在shared memory中地址映射的方式也是这样的
image

其中0-31为bank编号,如果申请一个共享内存数组__shared__ int cache[64],int 恰好为4个字节,那么cache[0]访问bank[0][0], cache[1]访问bank[0][1],...,cache[31]访问bank[0][31],cache超过32时,cache就会去访问下一行的bank,即cache[32]就会访问bank[1][0],以此类推。

bank冲突就是在这样的条件下产生,即如果一个warp的多个线程访问同一个bank的不同字段时(注:不同字段如bank[0][0],bank[1][0],...,bank[n][0]),那么就发生了bank冲突,因为不同bank可以同时访问,而当如果多个线程请求的内存地址被映射到了同一个bank上,那么这些请求就变成了串行的。

利用空闲线程

展开最后一维减少同步

完全循环展开

OpenCore使用

调整block大小

shuffle指令

NV提出了Shuffle指令,对于reduce优化有着非常好的效果。目前绝大多数访存类算子,像是softmax,batch_norm,reduce等,都是用Shuffle实现。所以,在这里谈一下这么把shuffle指令用在reduce优化上。

Shuffle指令是一组针对warp的指令。Shuffle指令最重要的特性就是warp内的寄存器可以相互访问。在没有shuffle指令的时候,各个线程在进行通信时只能通过shared memory来访问彼此的寄存器。而采用了shuffle指令之后,warp内的线程可以直接对其他线程的寄存器进行访存。通过这种方式可以减少访存的延时。除此之外,带来的最大好处就是可编程性提高了,在某些场景下,就不用shared memory了。毕竟,开发者要自己去控制 shared memory还是挺麻烦的一个事。

关于shuffle指令见:shuffle

L1 cache

L1 cache 同样需要合并访存才能发挥最大带宽
L1 cache 是用于缓存全局内存和局部内存的缓存,其内存读取细粒度是128 bytes,所以内存对齐的访问地址最好是128 bytes的倍数;且访问的数据最好是一次128 bytes(100%带宽利用率), 再大也只能逐次次访问,因为L1 cache line是128 bytes

nvcc -g -G -Xptxas -dlcm=ca -arch=sm_37 test.cu -o test

-Xptxas -dlcm=ca 开启L1 Cache
-Xptxas -dlcm=cg 禁用L1 Cache

向量化的访存

可以通过使用向量 load 指令来一次读取多个数据,float4 float2 int4 等数据类型的使用,nvcc编译器会在指令选择的时候选择向量 load 指令

__global__ void vectorizedMemoryAccess(float* input, float* output, int size) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    int idx = tid * 4;  // float4向量化访存

    if (idx < size) {
        float4 data = reinterpret_cast<float4*>(input)[tid];
        float sum = data.x + data.y + data.z + data.w;
        output[tid] = sum;
    }
}
posted @ 2023-03-14 17:09  nanmi  阅读(86)  评论(0编辑  收藏  举报