CUDA编程一、基本概念和cuda向量加法

news2025/1/19 16:59:45

       

目录

一、cuda编程的基本概念入门

1、GPU架构和存储结构

2、cuda编程模型

3、cuda编程流程

二、cuda向量加法实践

1、代码实现

2、代码运行和结果


        有一段时间对模型加速比较感兴趣,其中的一块儿内容就是使用C++和cuda算子优化之类一起给模型推理提速。之前一直没有了解过cuda编程,也没有学习过C++相关的东西。强迫自己来学习一下cuda编程,同时也学习一下C++,扩宽一下AI相关的领域知识。主要是能够理解怎么使用cuda来提升模型的推理速度,学习的目标就是要会使用cuda编程实现基本的向量加法乘法、能使用C++和cuda混合编程实现神经网络的一些基本模块、最终能够完成C++语言和cuda混合编程(自己实现算子或者调用英伟达成熟的库)完成一个LLM模型的前向推理过程。这里是cuda编程的第一篇入门篇,了解基本概念、gpu的架构、cuda编程模型和实现cuda向量加法,对cuda编程有一个基础的了解和实践。

一、cuda编程的基本概念入门

1、GPU架构和存储结构

GPU全称图形处理器(graphics processing unit),主要是做图像和图像等涉及到并行计算的微处理器。 GPU和CPU同样有自己的架构,GPU更重计算、CPU更重逻辑控制。从硬件层面来说,GPU的内部构成如下图——详解GPU:

GPU通常包括图形显存控制器、压缩单元、BIOS、图形和计算阵列、总线接口、电源管理单元、视频管理单元、显示接口等,我们用来做深度学习就主要用到了它的图形和计算阵列模块。

GPU微架构

从微架构角度来说,GPU是有一个个SM(Streaming Multiprocessors)构成的。如下图:

这是A100 安培架构显卡的SM内部结构图,SM由L1缓存、指令缓存、寄存器(Register)和Wrap scheduler等构成。图中的绿色部分是tensor core 也可以称作SP(Streaming processor),用于浮点数的计算,它可以支持一个时钟周期完成两个16×16矩阵的乘法操作,其他版本如Volta完成两个两个4×4半精度浮点矩阵的计算、Turing完成64个半精度浮点的乘加操作,总之计算速度更慢。

内存模型

GPU的内存也是多层级结构的,具体结构如下:

通用内存DRAM目前最好的显卡采用了HBM(High Bandwidth Memory 高带宽内存);更近一级的是L2缓存,所有的SM共享;L2之上就是L1缓存,SM独有,所有的显存共享;L1之上的就是寄存器,线程独有的。如图,英伟达的cuda编程指南中给出示意图:

2、cuda编程模型

GPU其实可以看做一个超多线程处理器,一个运行多次使用不同数据执行的程序,可以使用很多不同的线程来执行,在GPU上就是把这个函数编译为设备的指令集——kernel核函数。cuda就是实现这样功能的一个代码库,可以让开发者使用高级语言来实现上述GPU的多线程并行执行,加速计算速度。

图中显示一个kernel会被grid中的线程块一起执行。这里就有几个概念,gird、block和thread。一个grid有多个block构成;一个block有多个thread构成。其中grid中的block有x/y/z三个维度,总数有最大值,每个维度上有各自的最大值,需要查阅当前的cuda规范。同时block中的线程也分x/y/z三个维度,总数有最大值,每个维度上有各自的最大值。一般来说,block中的线程数最大为1024个。线程的序号由block数目和线程在block中的位置,对于上述kernel1,thread(4,2)来说,线程Id

threadId

=(threadIdx.x+threadIdx.y*blockDim.x)+(blockIdx.x+blockIdx.y*gridDim.x)*(blockDim.x*blockDim.y)

=(4+2*5)+(1+1*3)*(5*3)=14+60=74

内存模型

线程在内存的使用是什么样的?GPU的内存模型如下:

block中的线程共用shared Mem,线程独立拥有寄存器和本地内存,其它的内存都是所有的block共享的。

3、cuda编程流程

cuda编程流程其实有点像我们使用GPU进行模型训练,模型训练中首先是模型加载和数据的处理;然后是把模型参数和数据都从CPU内存移动到GPU内存(显存)上;最后进行模型训练。那cuda编程宏观上也是这么个逻辑,这里摘抄一段知乎博主小小将博文《CUDA编程入门极简教程》总结的流程如下:

  1. 分配host内存,并进行数据初始化;
  2. 分配device内存,并从host将数据拷贝到device上;
  3. 调用CUDA的核函数在device上完成指定的运算;
  4. 将device上的运算结果拷贝到host上;
  5. 释放device和host上分配的内存。

cuda编程的重点和难点也在于第三步cuda核函数的设计实现(设计一个跑通的可能不难但是设计一个高效率的可能就很难了),核函数的定义如下:

__global__ void kernelFunction(float *result, float *a, float *b){
    doSomething
}

使用__global__对核函数进行限定,表示该函数是一个GPU核函数,在GPU的线程中被执行。

使用一个核函数整体的代码流程如下:

__global__ void kernelFunction(float *result, float *a, float *b){
    doSomething
}
int main(){
    ......
    // 分配内存和显存
    cudaMallocManaged();
    //数据初始化
    initWith();
    // 每一个gird有多少个block 最大2^31-1   x方向最大2^31-1  y,z 方向65535
    dim3 gridDim(x,y,z);
    // 每一个block有多少个线程  最大1024  x,y方向最大1024  z最大64
    dim3 blockDim(x,y,z);
    //执行核函数
    kernelFunction <<< gridDim, blockDim >>>();
    cudaDeviceSynchronize(); // 同步
    ......
}

使用kernelFunction<<<gridDim,blockDim>>>()来指定对应的gridDim和blockDim并且启动和函数。根据wiki的数据显示:每一个gird有多少个block 最大2^31-1   x方向最大2^31-1  y,z 方向65535; 每一个block有多少个线程  最大1024  x,y方向最大1024  z最大64。

二、cuda向量加法实践

1、代码实现

       接下来基于cuda来实现两个一维矩阵(一维向量)的加法。按照上述cuda编程流程,首先需要进行数据初始化,然后把数据传输到GPU上,然后进行cuda核函数的计算,最后得到结果释放资源。首先看一下数据怎么在CPU和GPU上灵活的传输,新版本的cuda有如下API:

cudaError_t cudaMallocManaged(void** ptr, size_t size)

该函数运行我们在内存和显存开辟size_t大小的空间,并智能的进行数据在CPU和GPU上的移动。

现在需要设计核函数,简单起见我们设置grid和block都为一维的,核函数逻辑就可以按照如下设计:

__global__ void addVectorskernel(float *result, float *a, float *b, int N){
    int index = threadIdx.x + blockIdx.x * blockDim.x;
    int stride = blockDim.x * gridDim.x;
    for (int i=index; i<N; i+=stride){
        result[i] = a[i] + b[i]; // 元素a[i] + 元素 b[i]
    }
    
}

其中blockDim就表示一个block中有多少个线程,gridDim表示一个grid(一个gpu)中有多个block,那么总线程数就是blockDim.x * gridDim.x,每个线程处理的向量元素就是N/(blockDim.x * gridDim.x),因此就会有内部的循环,循环的步长也是总线程数blockDim.x * gridDim.x。当N=102400000,blockDim.x= 256,gridDim.x = 10,a矩阵的值全为3.0,b矩阵的值全为4.0,那么就可以得到如下代码nvcc_vector_add.cu:

#include<stdio.h>
#include<assert.h>
#include<cstdio>
#include<sys/time.h>
#include<iostream>
// 编译加链接
// nvcc -o nvcc_vector_add.cu  nvcc_vector_add.o
// 直接运行即可
// 向量加法核函数
__global__ void addVectorskernel(float *result, float *a, float *b, int N){
    int index = threadIdx.x + blockIdx.x * blockDim.x;
    int stride = blockDim.x * gridDim.x;
    for (int i=index; i<N; i+=stride){
        result[i] = a[i] + b[i]; // 元素a[i] + 元素 b[i]
    }
}
// 初始化数组 a
void initWith(float num, float *a, int N) {
    for(int i = 0; i < N; ++i) {
      a[i] = num;
    }
  };

int main(){
    const int N = 102400000;
    const int M = 10;
    size_t Mem = N * sizeof(float);
    float *a;
    float *b;
    float *c;

    cudaMallocManaged(&a, Mem);
    cudaMallocManaged(&b, Mem);
    cudaMallocManaged(&c, Mem);

    initWith(3.0, a, N); // 将数组a中所有的元素初始化为3
    initWith(4.0, b, N); // 将数组b中所有的元素初始化为4
    initWith(0.0, c, N); // 将数组c中所有的元素初始化为0,数组c是结果向量

    for(int i=0;i<M;i++){
        printf("%f ",a[i]);
    }
    printf("\n");
    printf("******************\n");
    for(int i=0;i<M;i++){
        printf("%f ",b[i]);
    }
    printf("\n");
    printf("******************\n");
    for(int i=0;i<M;i++){
        printf("%f ",c[i]);
    }
    printf("\n");
    printf("******************\n");

    // 配置参数
    size_t threadsPerBlock = 256;
    // size_t numberOfBlocks = (N + threadsPerBlock - 1) / threadsPerBlock;
    size_t numberOfBlocks = 10;

    struct timeval start;
    struct timeval end;
    gettimeofday(&start,NULL);
    addVectorskernel <<< numberOfBlocks, threadsPerBlock >>> (c, a, b, N); // 执行核函数
    cudaDeviceSynchronize(); // 同步,且检查执行期间发生的错误
    gettimeofday(&end,NULL);
    float time_use;
    time_use=(end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec);//微秒
    std::cout <<"vector_add gpu time cost is "<<time_use/1000/100<< " ms"<< std::endl;


    for(int i=0;i<M;i++){
        printf("%f ",a[i]);
    }
    printf("\n");
    printf("******************\n");
    for(int i=0;i<M;i++){
        printf("%f ",b[i]);
    }
    printf("\n");
    printf("******************\n");
    for(int i=0;i<M;i++){
        printf("%f ",c[i]);
    }
    printf("\n");
    printf("******************\n");
    return 0;
}

2、代码运行和结果

以上是一个.cu单文件,怎么运行它呢?需要使用英伟达的cuda环境进行编译和链接。在安装cuda toolkit的环境下,使用nvcc编译器进行编译:

//编译和链接
nvcc nvcc_vector_add.cu -o nvcc_vector_add

//运行
./nvcc_vector_add

参数-o表示生成可执行文件,编译结果如下:

编译后生成一个可执行文件,直接运行该可执行文件,得到如下结果:

可以看到结果正确,耗时为2ms,显存使用384M。

调整一下blockDim、gridDim的大小看看耗时的变化情况。

blockDim=256,gridDim=5

vector_add gpu time cost is 2.23614 ms

blockDim=256,gridDim=10

vector_add gpu time cost is 2.01713 ms

blockDim=256,gridDim=20

vector_add gpu time cost is 2.05501 ms

blockDim=128,gridDim=10

vector_add gpu time cost is 2.1049 ms

blockDim=512,gridDim=10

vector_add gpu time cost is 2.027 ms

以上结果具有误差因为都只跑了一次,没有多次求平均值,但是可以说明gridDim和blockDim对性能是有影响的。一般来说blockDim选择为32的倍数,因为一个wrap的线程束是32,blockDim这样设置可以减少bank conflict。

CUDA编程入门极简教程

Cuda Core VS Tensor Core

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1224610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适用于 Windows 的 10 个最佳视频转换器:快速转换高清视频

您是否遇到过由于格式不兼容而无法在您的设备上播放视频或电影的情况&#xff1f;您想随意播放从您的相机、GoPro 导入的视频&#xff0c;还是以最合适的格式将它们上传到媒体网站&#xff1f;您的房间里是否有一堆 DVD 光盘&#xff0c;想将它们转换为数字格式以便于播放&…

算法 LeetCode 题解 | 有效的括号

大家好&#xff0c;我是木川 一、题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。…

掌握Shell:从新手到编程大师的Linux之旅

1 shell介绍 1.1 shell脚本的意义 1.记录命令执行的过程和执行逻辑&#xff0c;以便以后重复执行 2.脚本可以批量处理主机 3.脚本可以定时处理主机 1.2 脚本的创建 #!/bin/bash # 运行脚本时候执行的环境1.3 自动添加脚本说明信息 /etc/vimrc # vim主配置文件 ~/.vimrc # 该…

Java之线程的概念及方法的学习

线程创建 方法一 直接使用Thread public class demo {public static void main(String[] args) {new Thread(){Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}.start();System.out.println(Thread.currentThread().getName());} } main…

深信服AC应用控制技术

拓扑图 目录 拓扑图 一.上班时间不允许使用qq(假设上班时间是上午9到12&#xff0c;下午14到18) 1.新增上班时间不允许使用qq访问权限策略 2.将策略应用到组&#xff0c;例如修仙部 3.验证 上班时间发现登录不了 下班时间可以登录 二.上班时间不允许访问视频网站(假设上班时…

2023年优化算法之之霸王龙优化算法(TROA),原理公式详解,附matlab代码

霸王龙优化算法&#xff08;Tyrannosaurus optimization&#xff0c;TROA&#xff09;是一种新的仿生优化算法&#xff0c;该算法模拟霸王龙的狩猎行为&#xff0c;具有搜索速度快等优势。该成果于2023年发表在知名SCI期刊e-Prime-Advances in Electrical Engineering, Electro…

Go vs Rust:文件上传性能比较

在本文中&#xff0c;主要测试并比较了Go—Gin和Rust—Actix之间的多部分文件上传性能。 设置 所有测试都在配备16G内存的 MacBook Pro M1 上执行。 软件版本为&#xff1a; Go v1.20.5Rust v1.70.0 测试工具是一个基于 libcurl 并使用标准线程的自定义工具&#xff0c;能…

【双指针】复写0

复写0 1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上…

ZYNQ_project:LCD

模块框图&#xff1a; 时序图&#xff1a; 代码&#xff1a; /* // 24h000000 4324 9Mhz 480*272 // 24h800000 7084 33Mhz 800*480 // 24h008080 7016 50Mhz 1024*600 // 24h000080 4384 33Mhz 800*480 // 24h800080 1018 70Mhz 1280*800 */ module rd_id(i…

html网页设计 01基础标签

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body> <!-- 标题标签 h1最大 --><h1>最大标签</h1><h2>二级标签</h2><h3>三级标签</h3><…

JavaScript管理HTMLDOM元素(增删改查)

本文主要讲解JavaScript如何通过管理HTML上的DOM元素&#xff0c;其中包括如何查询、创建、修改以及删除具体功能和源码讲解。 增加 首先我们准备一个HTML框架和简单CSS样式&#xff0c;我对其中元素作用和关系进行一个简单说明。 <!DOCTYPE html> <html><he…

OpenCV C++ 图像 批处理 (批量调整尺寸、批量重命名)

文章目录 图像 批处理(调整尺寸、重命名)图像 批处理(调整尺寸、重命名) 拿着棋盘格,对着相机变换不同的方角度,采集十张以上(以10~20张为宜);或者棋盘格放到桌上,拿着相机从不同角度一通拍摄。 以棋盘格,第一个内焦点为坐标原点,便于计算世界坐标系下三维坐标; …

提升 Python 执行速度:Codon、C/C++、Rust、Numba(JIT)、Taichi、Nuitka、MatxScript

几种流行的 Python 性能加速方案对比&#xff1a;https://zhuanlan.zhihu.com/p/604519817 对于一般通用场景用户&#xff0c;对性能没有那么强烈的诉求&#xff0c;紧跟官方步伐&#xff0c;升级到最新版本的 Python 既可&#xff0c;或者使用 PyPy。Numba、Codon、Taichi 等这…

IoC DI

Spring 的两大核心思想 : IoC 和 AOP 我们要将对象的控制权交给Spring ,我们就需要告诉 Spring 哪些对象是需要帮我们进行创建的,这里有两类注解可以实现 : 类注解(Controller Service Repository Component Configuration)和方法注解(Bean) 这五大注解都表示把这个对象交给…

【MySQL】InnoDB和MyISAM区别详解(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

【Java】ArrayList和LinkedList使用不当,性能差距会如此之大!

文章目录 前言源码分析ArrayList基本属性初始化新增元素删除元素遍历元素 LinkedList实现类基本属性节点查询新增元素删除元素遍历元素 分析测试 前言 在面试的时候&#xff0c;经常会被问到几个问题&#xff1a; ArrayList和LinkedList的区别&#xff0c;相信大部分朋友都能回…

C++之set/multise容器

C之set/multise容器 set基本概念 set构造和赋值 #include <iostream> #include<set> using namespace std;void PrintfSet(set<int>&s) {for(set<int>::iterator it s.begin();it ! s.end();it){cout<<*it<<" ";}cout&l…

保姆级 | Nginx编译安装

0x00 前言 Nginx 是一个 HTTP 和反向代理服务器&#xff0c; 邮件代理服务器&#xff0c; 和通用 TCP/UDP 代理服务器&#xff0c; 最初由伊戈尔西索耶夫&#xff08;Igor Sysoev&#xff09;撰写。采用编译安装可以根据自身需要自定义配置&#xff0c;让服务器有更高的安全性和…

2023腾讯云轻量应用服务器购买优惠活动,轻量服务器优惠链接

双11优惠活动即将到来&#xff0c;各大电商平台纷纷推出超值优惠&#xff0c;腾讯云也不例外。今天&#xff0c;我将向大家介绍一款在双11活动中备受瞩目的服务器套餐——腾讯云的3年轻量应用服务器配置为2核2G4M带宽、50GB SSD系统盘。这款服务器不仅配置强大&#xff0c;而且…

Mac 安装 protobuf 和Android Studio 使用

1. 安装,执行命令 brew install protoc 2. Mac 错误提示&#xff1a;zsh: command not found: brew解决方法 解决方法&#xff1a;mac 安装homebrew&#xff0c; 用以下命令安装&#xff0c;序列号选择中科大&#xff08;1&#xff09;或 阿里云 /bin/zsh -c "$(curl…