GPU高性能面试-写一个ReduceKernel

要求写一个reduceKernel 要求给出Kerne的完整调用:

1. 进行一维reduce 要求如下:

  1. 可以写一个最基础的,仅仅实现基础功能就行

  2. 使用share mem进行功能优化

  3. 使用shuffles指令完成block reduce操作

// 简单的实现,使用一个block完成Reduce操作 好蠢好蠢的代码
template<typename T>
__global__ void BlockReduce(const T* input, T* output, int num) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    for(int i = idx; i < num; i+= blockDim.x + gridDim.x) {
       atomicAdd(output, input +i); 
    }
}
# 一个block完成reduce操作 使用sharemem blockSize = 256

__global__ void BlockReduceSharemem(const T* input, T* output, int num) {

    int idx  =  threadIdx.x;
    __share__ int sharemem[ThreadPerBlock];
    sharemem[threadIdx.x] = 0;
    __syncthreads();
    for(int index = idx; index < num; index+=blockDim.x) {
        sharemem[threadIdx.x] += input[index];
    }
    __syncthreads();
    for(int i = blockDim.x /2 ; i > 0; i >> 1) {
       if (threadIdx < i) {
           sharemem[threadIdx.x] += sharemem[threadIdx.x + i];
       }
       syncthread();    
    }
    if (threadIdx.x == 0)
    output[0] = sharemem[0];
}
# 一个block完成reduce操作 使用shuffle指令 blockSize = WarpSize
# shfl指令只能完成一个warp内的操作,因此如果是进行一个block内的数据操作需要先,分步进行

__global__ void BlockReduceSharemem(const T* input, T* output, int num) {

    int idx  =  threadIdx.x;
    T sum = 0;
    for(int index = idx; index < num; index+=blockDim.x) {
        sum += input[index];
    }
    
    for(int i = WarpSize /2; i > 0; i >> 1) {
        sum+=__shfl_down(sum, i); 
    }
    if (threadIdx.x == 0)
    output[0] = sum;
}
# 一个block完成reduce操作 使用shuffle指令 blockSize % WarpSize = 0
# shfl指令只能完成一个warp内的操作,因此如果是进行一个block内的数据操作需要先,分步进行


__global__ void BlockReduceSharemem(const T* input, T* output, int num) {

    int idx  =  threadIdx.x;
    T sum = 0;
    for(int index = idx; index < num; index+=blockDim.x) {
        sum += input[index];
    }
    __share__ T data[BlockSize];
    data[idx] = 0;
    __syncthreads();
    # 先使用sharemem完成warp_size 以外的数据处理
    for(int i = blockDim.x/ 2 i > WarpSize; i>>1) {
       if (idx < i){
           data[idx] += data[idx + i];
           data[idx + i] = 0;
       }
    }
    __syncthreads();
    sum = data[idx];
    __syncthreads();

    for(int i = WarpSize /2; i > 0; i >> 1) {
        sum+=__shfl_down(sum, i); 
    }

    if (threadIdx.x == 0)
    output[0] = sum;
}

2.实现二维reduce 要求如下:

所谓而维Reduce是指当输入in[h, l] 两维时,对h进行Reduce操作,这个时候就需要考虑到线程映射问题了。

方法一: 如果将threadIdx.x 映射到I维度,threadIdx.y 映射到 h维, 则在进行数据读取时候就能实现连续读取,但是则无法进行blockX内的Reduce操作,可以使用sharemem转置再进行reduce

#include <cuda_runtime.h>
#include <vector>
#include <iostream>


template<typename T>
__inline__ __device__ T warpReduceSum(T x) {
    #pragma unroll
    for (int offset = 16; offset > 0; offset /= 2) 
        x += __shfl_down_sync(0xFFFFFFFF, x, offset);
    return x;
}

__global__ void reduce_col(
    const int* in, //(m, n) 
    const int m, 
    const int n, 
    int* out //(1, n)
){
    int tidx = threadIdx.x + blockDim.x * blockIdx.x;
    int tidy = threadIdx.y; 

    __shared__ int cache[32][32];//为了方便实现和理解用32*32线程数,也可以改成16*64或其他配置
    
    //每一列先累加到一个block中
    int sum = 0;
    if(tidx < n){
        for(int i = tidy; i < m; i += blockDim.y){
            sum += in[i * n + tidx];
        }
    }
    
    //将累加结果做转置,方便做warp reduce
    cache[threadIdx.x][threadIdx.y] = sum;
    __syncthreads();

    //block内每一行做reduce,的到32个结果
    int x = cache[threadIdx.y][threadIdx.x];	
	x = warpReduceSum<int>(x);
    __syncthreads();

    if(threadIdx.x == 0){
        out[blockIdx.x * blockDim.x + threadIdx.y] = x;
    }
}

void print(std::vector<int>& data, const int m, const int n){
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            std::cout << data[i * n + j] << " ";
        }
        std::cout << std::endl;
    }
    std::cout << std::endl;
}

int main(){
    const int m = 32;
    const int n = 32;
    std::vector<int> h_in(m * n), h_out(n, 0);

    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            h_in[i * n + j] = i * n + j;
            h_out[j] += h_in[i * n + j];
        }
    }
    print(h_in, m, n);

    int *d_in, *d_out;
    cudaMalloc(&d_in, m*n * sizeof(int));
    cudaMalloc(&d_out, n * sizeof(int));

    cudaMemcpy(d_in, h_in.data(), h_in.size() * sizeof(int), cudaMemcpyHostToDevice);
    
    dim3 blockDim(32, 32, 1);
    dim3 gridDim((n + 31) / 32, 1, 1);
    reduce_col<<<gridDim, blockDim>>>(d_in, m, n, d_out);

    std::vector<int> check_out(n);
    cudaMemcpy(check_out.data(), d_out, n * sizeof(int), cudaMemcpyDeviceToHost);
    print(h_out, 1, n);
    print(check_out, 1, n);
    return 0;
}

方法二: 如果将threadIdx.x 映射到h维度,threadIdx.y 映射到 l 维, 能够进行block内的数据读取,但是访存不连续。

补充优化:在进行Reduce index计算时 可以考虑更加高效的index方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值