• GPU高性能面试-写一个ReduceKernel


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

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

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

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

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

    1. // 简单的实现,使用一个block完成Reduce操作 好蠢好蠢的代码
    2. template<typename T>
    3. __global__ void BlockReduce(const T* input, T* output, int num) {
    4. int idx = blockIdx.x * blockDim.x + threadIdx.x;
    5. for(int i = idx; i < num; i+= blockDim.x + gridDim.x) {
    6. atomicAdd(output, input +i);
    7. }
    8. }
    1. # 一个block完成reduce操作 使用sharemem blockSize = 256
    2. __global__ void BlockReduceSharemem(const T* input, T* output, int num) {
    3. int idx = threadIdx.x;
    4. __share__ int sharemem[ThreadPerBlock];
    5. sharemem[threadIdx.x] = 0;
    6. __syncthreads();
    7. for(int index = idx; index < num; index+=blockDim.x) {
    8. sharemem[threadIdx.x] += input[index];
    9. }
    10. __syncthreads();
    11. for(int i = blockDim.x /2 ; i > 0; i >> 1) {
    12. if (threadIdx < i) {
    13. sharemem[threadIdx.x] += sharemem[threadIdx.x + i];
    14. }
    15. syncthread();
    16. }
    17. if (threadIdx.x == 0)
    18. output[0] = sharemem[0];
    19. }
    1. # 一个block完成reduce操作 使用shuffle指令 blockSize = WarpSize
    2. # shfl指令只能完成一个warp内的操作,因此如果是进行一个block内的数据操作需要先,分步进行
    3. __global__ void BlockReduceSharemem(const T* input, T* output, int num) {
    4. int idx = threadIdx.x;
    5. T sum = 0;
    6. for(int index = idx; index < num; index+=blockDim.x) {
    7. sum += input[index];
    8. }
    9. for(int i = WarpSize /2; i > 0; i >> 1) {
    10. sum+=__shfl_down(sum, i);
    11. }
    12. if (threadIdx.x == 0)
    13. output[0] = sum;
    14. }
    1. # 一个block完成reduce操作 使用shuffle指令 blockSize % WarpSize = 0
    2. # shfl指令只能完成一个warp内的操作,因此如果是进行一个block内的数据操作需要先,分步进行
    3. __global__ void BlockReduceSharemem(const T* input, T* output, int num) {
    4. int idx = threadIdx.x;
    5. T sum = 0;
    6. for(int index = idx; index < num; index+=blockDim.x) {
    7. sum += input[index];
    8. }
    9. __share__ T data[BlockSize];
    10. data[idx] = 0;
    11. __syncthreads();
    12. # 先使用sharemem完成warp_size 以外的数据处理
    13. for(int i = blockDim.x/ 2 i > WarpSize; i>>1) {
    14. if (idx < i){
    15. data[idx] += data[idx + i];
    16. data[idx + i] = 0;
    17. }
    18. }
    19. __syncthreads();
    20. sum = data[idx];
    21. __syncthreads();
    22. for(int i = WarpSize /2; i > 0; i >> 1) {
    23. sum+=__shfl_down(sum, i);
    24. }
    25. if (threadIdx.x == 0)
    26. output[0] = sum;
    27. }

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

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

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

    1. #include <cuda_runtime.h>
    2. #include <vector>
    3. #include <iostream>
    4. template<typename T>
    5. __inline__ __device__ T warpReduceSum(T x) {
    6. #pragma unroll
    7. for (int offset = 16; offset > 0; offset /= 2)
    8. x += __shfl_down_sync(0xFFFFFFFF, x, offset);
    9. return x;
    10. }
    11. __global__ void reduce_col(
    12. const int* in, //(m, n)
    13. const int m,
    14. const int n,
    15. int* out //(1, n)
    16. ){
    17. int tidx = threadIdx.x + blockDim.x * blockIdx.x;
    18. int tidy = threadIdx.y;
    19. __shared__ int cache[32][32];//为了方便实现和理解用32*32线程数,也可以改成16*64或其他配置
    20. //每一列先累加到一个block
    21. int sum = 0;
    22. if(tidx < n){
    23. for(int i = tidy; i < m; i += blockDim.y){
    24. sum += in[i * n + tidx];
    25. }
    26. }
    27. //将累加结果做转置,方便做warp reduce
    28. cache[threadIdx.x][threadIdx.y] = sum;
    29. __syncthreads();
    30. //block内每一行做reduce,的到32个结果
    31. int x = cache[threadIdx.y][threadIdx.x];
    32. x = warpReduceSum<int>(x);
    33. __syncthreads();
    34. if(threadIdx.x == 0){
    35. out[blockIdx.x * blockDim.x + threadIdx.y] = x;
    36. }
    37. }
    38. void print(std::vector<int>& data, const int m, const int n){
    39. for(int i = 0; i < m; i++){
    40. for(int j = 0; j < n; j++){
    41. std::cout << data[i * n + j] << " ";
    42. }
    43. std::cout << std::endl;
    44. }
    45. std::cout << std::endl;
    46. }
    47. int main(){
    48. const int m = 32;
    49. const int n = 32;
    50. std::vector<int> h_in(m * n), h_out(n, 0);
    51. for(int i = 0; i < m; i++){
    52. for(int j = 0; j < n; j++){
    53. h_in[i * n + j] = i * n + j;
    54. h_out[j] += h_in[i * n + j];
    55. }
    56. }
    57. print(h_in, m, n);
    58. int *d_in, *d_out;
    59. cudaMalloc(&d_in, m*n * sizeof(int));
    60. cudaMalloc(&d_out, n * sizeof(int));
    61. cudaMemcpy(d_in, h_in.data(), h_in.size() * sizeof(int), cudaMemcpyHostToDevice);
    62. dim3 blockDim(32, 32, 1);
    63. dim3 gridDim((n + 31) / 32, 1, 1);
    64. reduce_col<<<gridDim, blockDim>>>(d_in, m, n, d_out);
    65. std::vector<int> check_out(n);
    66. cudaMemcpy(check_out.data(), d_out, n * sizeof(int), cudaMemcpyDeviceToHost);
    67. print(h_out, 1, n);
    68. print(check_out, 1, n);
    69. return 0;
    70. }

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

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

  • 相关阅读:
    spring boot 2.7 集成 swagger 3
    Linux系统中如何安装生信软件?保姆式全攻略
    NFT 为何能够重塑艺术价值?
    基于 IDEA 搭建 RocketMQ-4.6 源码环境
    数据仓库Hive(林子雨课程慕课)
    大学生静态HTML网页源码 我的校园网页设计成品 学校班级网页制作模板 web课程设计 dreamweaver网页作业
    [附源码]SSM计算机毕业设计学生宿舍设备报修JAVA
    将某路径下文件名批量获取填入表格中
    HTML1:html基础
    java使用RestHighLevelClient操作elasticsearch客户端异常Content-Type: text/html
  • 原文地址:https://blog.csdn.net/m0_38086244/article/details/133808469