【CUDA基础练习】向量内积计算的若干种方法

2023-11-05


        先从一个简单,直观的方法来了解如何用CUDA计算向量内积。向量内积既然是将两个向量对应元素相乘的结果再求和,我们先考虑将对应元素相乘并行化,再来考虑相加。

【方法一】

#include<stdio.h>
#include<iostream>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "error.cuh"

#define VECTOR_LENGTH 10000

__global__ void vec_product(float *out, float *vec_a, float *vec_b, int n) 
{
    extern __shared__ float temp[];
    int idx = threadIdx.x;
    int stride = blockDim.x;

    for (int i=idx; i<n; i+=stride) 
    {
        temp[i] = vec_a[i] * vec_b[i];
    }
    __syncthreads();

    if (threadIdx.x == 0) 
    {
        float sum = 0;
        for (int i=0; i<n; i++) 
        {
            sum += temp[i];
        }
        *out = sum;
    }
}


int main()
{
    //cuda_info();
    float *a, *b, *out; 
    a   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    b   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    out = (float*)malloc(sizeof(float));
    *out = 0;

    for(int i=0; i<VECTOR_LENGTH; i++)
    {
        a[i] = 2.0f;
        b[i] = 2.0f;
    }

    float *d_a,*d_b,*d_out;
    cudaMalloc((void**)&d_a, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_b, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_out, sizeof(float));

    cudaMemcpy(d_a,a,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);
    cudaMemcpy(d_b,b,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);

    cudaEvent_t start,stop;
    CHECK(cudaEventCreate(&start));
    CHECK(cudaEventCreate(&stop));
    CHECK(cudaEventRecord(start));
    cudaEventQuery(start);
    
    int minGridSize,blockSize;
    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, vec_product, VECTOR_LENGTH*sizeof(float), 0);
    printf("minGridSize:%d,blockSize:%d\n",minGridSize,blockSize);

    vec_product<<<1,blockSize, sizeof(float)*VECTOR_LENGTH>>>(d_out, d_a, d_b, VECTOR_LENGTH);

    CHECK(cudaEventRecord(stop));
    CHECK(cudaEventSynchronize(stop));
    float elapsed_time;
    CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
    printf("time cost: %.3f\n", elapsed_time);

    cudaMemcpy(out,d_out,sizeof(float),cudaMemcpyDeviceToHost);
    cudaDeviceSynchronize();
    
    printf("vector product: %f\n", *out); 

    free(a);
    free(b);
    free(out);
    cudaFree(a);
    cudaFree(b);
    cudaFree(out);
    return 0;
}

        方法一中网格大小为1,线程块的大小根据由cudaOccupancyMaxPotentialBlockSize计算得到,对于这个简单示例来说,你也可以自己定义一个线程块的大小,一个线程块中的线程并行地执行。上述代码中,从头至尾只有一个线程块在运行。对于大小为N的向量,第1个线程处理第0,blocksize,2*blocksize,....号位置上的元素,第2个线程处理第1,1+blocksize,1+2*blocksize,...号位置上的元素。对应元素的乘积结果保存在一个长度为N的共享内存上面。等线程块内的所有线程完成乘机运算后,在第一个线程上将沉积的结果加起来并返回结果。

【nvpro测试】

==5245== NVPROF is profiling process 5245, command: ./sample_v1
minGridSize:16,blockSize:1024
time cost: 0.116
vector product: 40000.000000
==5245== Profiling application: ./sample_v1
==5245== Profiling result:
            Type  Time(%)      Time     Calls       Avg       Min       Max  Name
 GPU activities:   88.81%  53.821us         1  53.821us  53.821us  53.821us  vec_product(float*, float*, float*, int)
                    9.08%  5.5030us         2  2.7510us  2.6550us  2.8480us  [CUDA memcpy HtoD]
                    2.11%  1.2800us         1  1.2800us  1.2800us  1.2800us  [CUDA memcpy DtoH]
      API calls:   99.74%  273.58ms         3  91.195ms  4.9430us  273.57ms  cudaMalloc
                    0.10%  262.02us        97  2.7010us     252ns  104.97us  cuDeviceGetAttribute
                    0.07%  195.46us         1  195.46us  195.46us  195.46us  cuDeviceTotalMem
                    0.02%  58.494us         3  19.498us  13.723us  29.006us  cudaMemcpy
                    0.02%  50.682us         1  50.682us  50.682us  50.682us  cudaEventSynchronize
                    0.02%  45.974us         1  45.974us  45.974us  45.974us  cuDeviceGetName
                    0.01%  28.094us         1  28.094us  28.094us  28.094us  cudaLaunchKernel
                    0.01%  17.564us         1  17.564us  17.564us  17.564us  cuDeviceGetPCIBusId
                    0.00%  9.0360us         2  4.5180us     815ns  8.2210us  cudaEventCreate
                    0.00%  7.9480us         2  3.9740us  3.8540us  4.0940us  cudaEventRecord
                    0.00%  4.2820us         1  4.2820us  4.2820us  4.2820us  cudaFuncGetAttributes
                    0.00%  3.3040us         1  3.3040us  3.3040us  3.3040us  cudaDeviceSynchronize
                    0.00%  3.2010us         3  1.0670us     609ns  1.9100us  cudaFree
                    0.00%  3.1560us         4     789ns     418ns  1.6510us  cudaDeviceGetAttribute
                    0.00%  2.5620us         3     854ns     353ns  1.4010us  cuDeviceGetCount
                    0.00%  1.8870us         1  1.8870us  1.8870us  1.8870us  cudaEventElapsedTime
                    0.00%  1.6540us         1  1.6540us  1.6540us  1.6540us  cudaOccupancyMaxActiveBlocksPerMultiprocessorWithFlags
                    0.00%  1.6410us         1  1.6410us  1.6410us  1.6410us  cudaEventQuery
                    0.00%  1.5970us         2     798ns     430ns  1.1670us  cuDeviceGet
                    0.00%  1.2450us         1  1.2450us  1.2450us  1.2450us  cudaGetDevice
                    0.00%     506ns         1     506ns     506ns     506ns  cuDeviceGetUuid

上面这种方法局限性很大。首先,CUDA中规定了一个线程块中的线程数量不能超过1024,所以你最多也就并行计算1024个位置上的数组对应元素的相乘。此外,一个线程块中可用的共享内存也是受限的,这种方法要求一个线程块中至少要sizeof(float)*N大小的共享内存,当数组长度更大是,会存在资源不足。所以,怎么把它扩展到多个block?可以考虑对数组按blockSize进行切分,如:[0,....blockSize-1],[blockSize,...,2*blockSize-1]....由多个block分别并行计算,方法二给出了核心代码。

【方法二】

#include<stdio.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "error.cuh"

#define VECTOR_LENGTH 10000

__global__ void vector_product(float *out, float *vec_a, float *vec_b, int N) 
{
    const int stride = blockDim.x;
    const int tid = threadIdx.x;
    const int idx = blockIdx.x*blockDim.x+ threadIdx.x;
    extern __shared__ float s_y[];

    s_y[tid] = (idx < N) ? vec_a[idx] * vec_b[idx] : 0.0;
    

    if (tid == 0) 
    {   
        float sum=0;
        for (int i=0; i<stride; i++) {
            sum += s_y[i];    
        }
        atomicAdd(out, sum);
    }
}

int main()
{
    GetInfo();
    float *a, *b, *out; 
    a   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    b   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    out = (float*)malloc(sizeof(float));
    *out = 0;

    for(int i=0; i<VECTOR_LENGTH; i++)
    {
        a[i] = 2.0f;
        b[i] = 2.0f;
    }

    int blockSize;
    int minGridSize;

    float *d_a,*d_b,*d_out;
    cudaMalloc((void**)&d_a, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_b, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_out, sizeof(float));

    cudaMemcpy(d_a,a,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);
    cudaMemcpy(d_b,b,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);


    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, vector_product, 0, 0);
    printf("minGridSize:%d,blockSize:%d\n",minGridSize,blockSize);
    minGridSize = (VECTOR_LENGTH + blockSize - 1)/blockSize;

    cudaEvent_t start,stop;
    CHECK(cudaEventCreate(&start));
    CHECK(cudaEventCreate(&stop));
    CHECK(cudaEventRecord(start));
    cudaEventQuery(start);

    vector_product<<<minGridSize,blockSize,sizeof(float)*blockSize>>>(d_out, d_a, d_b, VECTOR_LENGTH);

    CHECK(cudaEventRecord(stop));
    CHECK(cudaEventSynchronize(stop));
    float elapsed_time;
    CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
    printf("time cost: %.3f\n", elapsed_time);

    cudaMemcpy(out,d_out,sizeof(float),cudaMemcpyDeviceToHost);
    cudaDeviceSynchronize();
    
    printf("vector product: %f\n", *out); 

    free(a);
    free(b);
    free(out);
    cudaFree(a);
    cudaFree(b);
    cudaFree(out);
    return 0;
}

【nvprof测试】

==26432== NVPROF is profiling process 26432, command: ./sample_v1_1

GPU has cuda devices: 1
----device id: 0 info----
  GPU : GeForce GTX 1650 
  Capbility: 7.5
  Global memory: 3911MB
  Const memory: 64KB
  Shared memory in a block: 48KB
  warp size: 32
  threads in a block: 1024
  block dim: (1024,1024,64)
  grid dim: (2147483647,65535,65535)

minGridSize:16,blockSize:1024
time cost: 0.036
vector product: 40000.000000
.....

代码中用到了CUDA编程中两项常用的技术:共享内存和原子操作。因为线程块的运行是独立的,在每个线程块的0号线程中完成所属block内的数组元素内积后要加到输出变量中去。这里使用了原子操作,保证线程之间互不干扰。好了,现在再来考虑一个问题。向量的乘法操作我们已经考虑了并行,但是元素相加仍然并行力度不够,只在每个线程块的0号线程上循化进行。怎么优化加法呢?我们引入归约的思想。对于一个线程块要处理的一段数组数据,我们将数组后半部分的各个元素与前半部分的各个元素相加。如果重复此过程,最后得到的第一个数组元素就是最初的数组中的各个元素的和,也就是所谓的折半归约。方法三实现了这一过程。

【方法三】

#include <stdio.h>
#include <algorithm>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "error.cuh"

#define VECTOR_LENGTH 10000

__global__ void vector_product(float *out, float *vec_a, float *vec_b, int N) 
{
    const int tid = threadIdx.x;
    const int idx = blockIdx.x*blockDim.x+ threadIdx.x;
    extern __shared__ float s_y[];

    s_y[tid] = (idx < N) ? vec_a[idx] * vec_b[idx] : 0.0;
    __syncthreads();
    
    for (int offset = blockDim.x>>1; offset>0; offset>>=1) 
    {
        if (tid < offset) 
        {
            s_y[tid] += s_y[tid + offset];
        }
        __syncthreads();
    }

    if (tid == 0) 
    {    
        atomicAdd(out, s_y[0]);
    }
}


int main()
{
    //GetInfo();
    float *a, *b, *out; 
    a   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    b   = (float*)malloc(sizeof(float) * VECTOR_LENGTH);
    out = (float*)malloc(sizeof(float));
    *out = 0;

    for(int i=0; i<VECTOR_LENGTH; i++)
    {
        a[i] = 2.0f;
        b[i] = 2.0f;
    }

    int blockSize;
    int minGridSize;

    float *d_a,*d_b,*d_out;
    cudaMalloc((void**)&d_a, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_b, sizeof(float)*VECTOR_LENGTH);
    cudaMalloc((void**)&d_out, sizeof(float));

    cudaMemcpy(d_a,a,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);
    cudaMemcpy(d_b,b,sizeof(float)*VECTOR_LENGTH,cudaMemcpyHostToDevice);
    cudaMemset(d_out,0,sizeof(float));

    cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, vector_product, 0, 0);

    cudaEvent_t start,stop;
    CHECK(cudaEventCreate(&start));
    CHECK(cudaEventCreate(&stop));
    CHECK(cudaEventRecord(start));
    cudaEventQuery(start);

    minGridSize = std::min(minGridSize,(VECTOR_LENGTH + blockSize - 1)/blockSize);
    printf("minGridSize:%d,blockSize:%d\n",minGridSize,blockSize);

    vector_product<<<minGridSize,blockSize,sizeof(float)*blockSize>>>(d_out, d_a, d_b, VECTOR_LENGTH);

    CHECK(cudaEventRecord(stop));
    CHECK(cudaEventSynchronize(stop));
    float elapsed_time;
    CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
    printf("time cost: %.3f\n", elapsed_time);

    cudaMemcpy(out,d_out,sizeof(float),cudaMemcpyDeviceToHost);
    cudaDeviceSynchronize();
    
    printf("vector product: %f\n", *out); 

    free(a);
    free(b);
    free(out);
    cudaFree(a);
    cudaFree(b);
    cudaFree(out);
    return 0;
}

【nvprof测试】

==30436== NVPROF is profiling process 30436, command: ./sample_v3
minGridSize:10,blockSize:1024
time cost: 0.036
vector product: 40000.000000

【附录】

GeForce GTX 1650 显卡 | NVIDIAGeForce GTX 1650 显卡拥有突破性的图形性能,能为热门游戏带来加速的游戏体验。https://www.nvidia.cn/geforce/graphics-cards/gtx-1650/

Device0:"GeForce GTX 1650"
Total amount of global memory                   4101898240 bytes
Number of mltiprocessors                        16
Total amount of constant memory:                65536 bytes
Total amount of shared memory per block         49152 bytes
Total number of registers available per block:  65536
Warp size                                       32
Maximum number of threads per block:            1024
Maximum sizes of each dimension of a block:     1024 x 1024 x 64
Maximum size of each dimension of a grid:       2147483647 x 65535 x 65535
Maximum memory pitch :                          2147483647 bytes
Texture alignmemt                               32 bytes
Clock rate                                      1.56 GHz


代码中先通过cudaGetDeviceCount来得到系统中NVIDIA GPU的数目
再通过函数cudaGetDeviceProperties,获取系统中GPU的属性;
deviceProp.name为GPU名字,如果没有GPU则会输出 Device Emulation
deviceProp.totalGlobalMem返回的是全局储存器的大小,对大数据或一些大模型计算时显存大小必须大于数据大小,如图返回的是2GB的存储大小,
deviceProp.multiProcessorCount返回的是设备中流多处理器(SM)的个数,流处理器(SP)的个数SM数×每个SM包含的SP数,其中帕斯卡为每个SM含有64个SP,麦克斯韦为128个,开普勒为192个,费米为32个,
deviceProp.totalConstMem返回的是常数储存器的大小,如同为64kB
deviceProp.sharedMemPerBlock返回共享储存器的大小,共享存储器速度比全局储存器快,
deviceProp.regsPerBlock返回寄存器的数目;
deviceProp.warpSize返回线程束中线程多少;
deviceProp.maxThreadsPerBlock返回一个block中最多可以有的线程数;
deviceProp.maxThreadsDim[]返回block内3维度中各维度的最大值
deviceProp.maxGridSize[]返回Grid内三维度中各维度的最大值;
deviceProp.memPitch返回对显存访问时对齐时的pitch的最大值;
deviceProp.texturePitchAlignment返回对纹理单元访问时对其参数的最大值;
deviceProp.clockRate返回显存的频率;

计算最佳grid/block对
cudaOccupancyMaxPotentialBlockSize (
    int *minGridSize,
    int *blockSize,
    T func,
    size_t dynamicSMemSize,
    int blockSizeLimit)
功能:
返回达到设备功能最大占用率时网格和块大小。
参数:
minGridSize : 返回达到最佳潜在占用率所需的最小网格尺寸。
blockSize : 返回块大小。
func :kernel函数名。
dynamicSMemSize : 每个block预定要使用的动态共享内存大小,单位是Byte。
blockSizeLimit :kernel在设计时可以使用的最大块大小,0表示没有限制。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【CUDA基础练习】向量内积计算的若干种方法 的相关文章

随机推荐

  • vscode快捷键:折叠、展开代码

    vscode代码编辑器折叠所有区域的代码快捷键 1 折叠所有区域代码的快捷键 ctrl k ctrl 0 先按下ctrl和K 再按下ctrl和0 注意这个是零 不是欧 2 展开所有折叠区域代码的快捷键 ctrl k ctrl J 先按下ct
  • Python 之 lambda 函数完整详解 & 巧妙运用

    文章目录 一 前言 二 lambda 语法 三 lambda 特性 四 lambda 常见用法 五 lambda 用法之高阶函数 一 前言 lambda 函数在 Python 编程语言中使用频率非常高 使用起来非常灵活 巧妙 那么 什么是l
  • php token 存储,PHP 实现Token无状态登录验证,无需服务端存储,降低服务器压力...

    早之前大家常用的登录方式是客户端传输账号密码 后台通过比对后成功了 客户端存储在cookie中 每次刷新页面或请求服务端 服务端都需要从数据库中调取对比信息 对于服务端来说 对比登录状态是服务端请求最频繁的动作 所以如果每次都调用数据库请求
  • java参数传递(局部变量)

    基本数据类型 传递的是值的副本 修改副本不会对原值造成影响 也就是值传递 Byte Short Integer Long Float Double Boolean Character 引用数据类型 除开基本数据类型的所有数据类型 传递的是对
  • centos8 安装notepadqq

    yum install epel release yum install snapd systemctl enable now snapd socket snap install notepad plus plus yum install
  • 【附源码】计算机毕业设计SSM汽车维修服务系统

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • FTP使用教程

    FTP使用教程 目录 一 FTP简介 二 FTP搭建 三 FTP使用 一 FTP简介 FTP中文为文件传输协议 简称为文传协议 它也是一个应用程序 不同的操作系统有不同的FTP应用程序 这些应用程序都遵守同一种协议以传输文件 FTP主要的功
  • KD树

    KD树 1 概述 KD树是一种查询索引结构 广泛应用于数据库索引中 从概念的角度讲 它是一种高纬数据的快速查询结构 本文首先介绍1维数据的索引查询 然后介绍2维KD树的创建和查询 2 1维数据的查询 假设在数据库的表格T中存储了学生的语文成
  • 数据挖掘概述

    1 数据挖掘定义 数据挖掘是从大量的 不完全的 有噪声的 模糊的 随机的应用数据中 提取出潜在且有用的信息的过程 2 数据分析的过程 1 确定知识发现的过程 2 数据采集 数据质量决定挖掘的上限 而算法仅仅是逼近这个上限 3 数据搜索 将数
  • 怎么做好中层管理

    随着工作经验和年限不断增加 经过努力相信很多IT技术人员和我一样从一个开发角色转变为技术管理或者部门管理角色 一方面需要做技术解决方案架构开发上工作 带领团队攻关技术难题 同时也会面临一些团队管理和团队业绩增长需要不断去提高和突破 虽然不必
  • 基于MATLAB的遗传算法优化混合流水车间调度问题

    基于MATLAB的遗传算法优化混合流水车间调度问题 混合流水车间调度问题是在车间生产中常见的一个优化问题 旨在通过合理安排作业顺序和机器分配 最大限度地提高生产效率和降低生产成本 本文将介绍如何使用MATLAB编写遗传算法来解决混合流水车间
  • Ubuntu ssh root 登录不了解决办法

    Ubuntu在安装的时候 设置了sudo的密码 但是这并不是root用户的密码 导致即使修改了 etc ssh sshd config 文件 运行root用户登录 还是会出现Permission denied 真是坑啊 差点没把我气到吐血
  • 发送html邮件时遇到的奇怪的乱码问题

    解析文件字符流 作为内容发送邮件到客户端 解析的代码如下 BufferedReader reader new BufferedReader new FileReader file 发现windows系统下发送的邮件时是正常的 在linux下
  • NETPLIER : 一款基于概率的网络协议逆向工具(一)理论

    本文系原创 转载请说明出处 信安科研人 关注微信公众号 信安科研人 获取更多网络安全学术技术资讯 今日介绍一篇发表在2021 NDSS会议上的一项有关协议逆向的工作 文章目录 1 网络协议逆向工程简介 1 1 协议逆向工程的主流技术 案例
  • 【计算机网络】第一章知识点汇总

    第一章学习重点 2 5 因特网的组成 通信方式与交换方式 4 4 时延 计算 4 5 时延带宽积 计算 5 2 协议的划分与层次 5 3 五层协议的体系结构 OSI与TCP IP的比较 1 计算机网络在信息时代的作用 三网融合中的 三网 指
  • Hadoop-HBase 单机部署

    一 系统版本 Linux系统 wdOS 1 0 x86 64 iso 关于wdOS说明 1 安装简单 快速 去掉了安装过程中不必要的烦锁操作和不必要的选择 2 可选安装集成web环境 如lamp lnmp lnamp 并可相互自由切换使用
  • 设计模式之适配器模式(Adapter)

    1 定义 适配器模式 Adapter 指的是将一个类的接口转换成另一个可以兼容的接口 比如我们日常生活中的转换头 古早时期使用的电池万能充 就相当于程序中使用的适配器模式 2 适配器模式的种类 2 1 类适配器模式 类适配器模式通过多重继承
  • 大数据项目实战——基于某招聘网站进行数据采集及数据分析(三)

    大数据项目实战 第三章 数据采集 文章目录 大数据项目实战 学习目标 一 分析与准备 1 分析网页结构 2 数据采集环境准备 二 采集网页数据 1 创建响应结果 JavaBean 类 2 封装 HTTP 请求的工具类 1 定义三个全局变量
  • 14张自动化测试框架结构图(建议收藏)

    1 接口自动化测试框架设计图 接口自动化测试框架设计图 2 接口自动化执行设计图 接口自动化执行设计图 3 API自动化平台框架设计图 API自动化平台框架设计图 4 UI自动化测试框架设计图 UI自动化测试框架设计图 5 接口 UI自动化
  • 【CUDA基础练习】向量内积计算的若干种方法

    先从一个简单 直观的方法来了解如何用CUDA计算向量内积 向量内积既然是将两个向量对应元素相乘的结果再求和 我们先考虑将对应元素相乘并行化 再来考虑相加 方法一 include