全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-05-09_我的第一份CUDA代码

您的位置:首页 >> 新闻 >> 行业资讯

我的第一份CUDA代码 作者丨xcyuyuyu来源丨https://zhuanlan.zhihu.com/p/507678214编辑丨极市平台注:内容仅做学术分享之用,若涉及侵权等行为,请联系删除,万分感谢!1. 前言这是一份简单的CUDA编程入门,主要参考英伟达的官方文档进行学习,本人也是刚开始学习,如有表述错误,还请指出。官方文档链接如下: https://developer.nvidia.com/blog/even-easier-introduction-cuda/ 本文先从一份简单的C++代码开始,然后逐步介绍如何将C++代码转换为CUDA代码,以及对转换前后程序的运行时间进行对比,本文代码放在我的github中,有需要可以自取。 https://github.com/xcyuyuyu/My-First-CUDA-Code 本文所使用的CPU为i7-4790,GPU为GTX 1080,那就开始吧。 2. 一份简单的C++代码首先是一份简单的C++代码,主要的运行函数为add函数,该函数实现功能为30M次的for循环,每次循环进行一次加法。 //add.cpp #includeiostream #includemath.h #includesys/time.h //functiontoaddtheelementsoftwoarrays voidadd(intn,float*x,float*y) { for(inti=0;ii++) y[i]=x[i]+y[i]; } intmain(void) { intN=125;//30Melements float*x=newfloat[N]; float*y=newfloat[N]; //initializexandyarraysonthehost for(inti=0;ii++){ x[i]=1.0f; y[i]=2.0f; } structtimevalt1,t2; doubletimeuse; gettimeofday(&t1,NULL); //Runkernelon30MelementsontheCPU add(N,x, gettimeofday(&t2,NULL); timeuse=(t2.tv_sec-t1.tv_sec)+(double)(t2.tv_usec-t1.tv_usec)/1000.0; std::cout"add(int,float*,float*)time:"timeuse"ms"std::endl; //Checkforerrors(allvaluesshouldbe3.0f) floatmaxError=0.0f; for(inti=0;ii++) maxError=fmax(maxError,fabs(y[i]-3.0f)); std::cout"Maxerror:"maxErrorstd::endl; //Freememory delete[] delete[] return0; } 编译以及运行代码: g++ add.cpp -o add ./add 不出意外的话,你应该得到下面的结果: 第一行表示add函数的运行时间,第二行表示每个for循环里的计算是否与预期结果一致。 这个简单的C++代码在CPU端运行,运行时间为85ms,接下来介绍如何将主要运算的add函数迁移至GPU端。 3. 把C++代码改成CUDA代码将C++代码改为CUDA代码,目的是将add函数的计算过程迁移至GPU端,利用GPU的并行性加速运算,需要修改的地方主要有3处: 1.首先需要做的是将add函数变为GPU可运行函数,在CUDA中称为kernel,为此,仅需将变量声明符添加到函数中,告诉 CUDA C++ 编译器这是一个在 GPU 上运行并且可以从 CPU 代码中调用的函数。 __global__ voidadd(intn,float*x,float*y) { for(inti=0;ii++) y[i]=x[i]+y[i]; } 那么修改后的add函数的调用也比较简单,仅需要在add函数名后面加上三角括号语法i,j指定CUDA内核启动即可,i,j称为执行配置(execution configuration),用于配置程序运行时的线程,后续会讲到,目前先将其设置为i,j: add1,1(N,x, 2. 那么为了在GPU进行计算,需要在GPU上分配可访问的内存。CUDA中通过Unified Memory(统一内存)机制来提供可同时供GPU和CPU访问的内存,使用cudaMallocManaged()函数进行分配: cudaMallocManaged(N*sizeof(float)); cudaMallocManaged(N*sizeof(float)); 同时,在程序最后使用cudaFree()进行内存释放: cudaFree(x); cudaFree(y); 其实就相当于C++中的new跟delete。 3. add函数在GPU端运行之后,CPU需要等待cuda上的代码运行完毕,才能对数据进行读取,因为CUDA内核启动时并未对CPU的线程进行固定,需要使用cudaDeviceSynchronize()函数进行同步。 4. 整体的程序如下所示: //add.cu #includeiostream #includemath.h //Kernelfunctiontoaddtheelementsoftwoarrays //__global__变量声明符,作用是将add函数变成可以在GPU上运行的函数 //__global__函数被称为kernel, //在 GPU 上运行的代码通常称为设备代码(device code),而在 CPU 上运行的代码是主机代码(host code)。 __global__ voidadd(intn,float*x,float*y) { for(inti=0;ii++) y[i]=x[i]+y[i]; } intmain(void) { intN=125; float*x, //AllocateUnifiedMemory–accessiblefromCPUorGPU //内存分配,在GPU或者CPU上统一分配内存 cudaMallocManaged(N*sizeof(float)); cudaMallocManaged(N*sizeof(float)); //initializexandyarraysonthehost for(inti=0;ii++){ x[i]=1.0f; y[i]=2.0f; } //Runkernelon1MelementsontheGPU //executionconfiguration,执行配置 add1,1(N,x, //WaitforGPUtofinishbeforeaccessingonhost //CPU需要等待cuda上的代码运行完毕,才能对数据进行读取 cudaDeviceSynchronize(); //Checkforerrors(allvaluesshouldbe3.0f) floatmaxError=0.0f; for(inti=0;ii++) maxError=fmax(maxError,fabs(y[i]-3.0f)); std::cout"Maxerror:"maxErrorstd::endl; //Freememory cudaFree(x); cudaFree(y); return0; } 使用nvcc对程序进行编译并运行: nvcc add.cu -o add_cuda ./add_cuda 或者使用nvprof进行速度测试: nvprof ./add_cuda 不出意外的话,你会得到以下输出: 框出来的就是add函数在GPU端的运行时间,为4s。没错,就是比CPU端85ms还要慢,那还学个锤子。 4. 使用CUDA代码并行运算好的回过头看看,问题出现在这个执行配置 i,j 上。不急,先看一下一个简单的GPU结构示意图,按照层次从大到小可将GPU按照 grid - block - thread划分,其中最小单元是thread,并行的本质就是将程序的计算模块拆分成多个小模块扔给每个thread并行计算。 再看一下前面执行配置 `i,j` 的含义,`i,j` 应该写成 `numBlocks, blockSize` ,即表示函数运行时使用的block数量以及每个block的大小,前面我们将其设置为`1,1` ,说明程序是单线程运行的,那当然慢了~~。下面我们以单个block为例,将其改为`1,256`,add函数也需要适当修改: __global__ voidadd(intn,float*x,float*y) { intindex=threadIdx.x;//threadIdx.x表示当前在第几个thread上运行 intstride=blockDim.x;//blockDim.x表示每个block的大小 for(inti=index;ii+=stride) y[i]=x[i]+y[i]; } 修改的部分也比较好理解,不赘述了,接下来运行看看结果: 你看,开始加速了吧,4s加速到了77ms。 那么,`numBlocks, blockSize` 的两个参数应该怎么设置好呢。首先,CUDA GPU 使用大小为 32 的倍数的线程块运行内核,因此 `blockSize` 的大小应该设置为32的倍数,例如128、256、512等。确定 `blockSize` 之后,可以根据for循环的总个数`N`确定 `numBlock` 的大小(注意四舍五入的误差): intnumBlock=(N+blockSize-1)/blockSize; 当然因为变成了多个`block`,所以此时add函数需要再改一下: __global__ voidadd(intn,float*x,float*y) { intindex=blockIdx.x*blockDim.x+threadIdx.x; intstride=blockDim.x*gridDim.x; for(inti=index;ii+=stride) y[i]=x[i]+y[i]; } 这里index跟stride的计算可以参考上面GPU结构图以及下面的图(图取自An Even Easier Introduction to CUDA | NVIDIA Technical Blog),自行推算,较好理解。 搞定之后再编译运行一下: 看看,又加速了不是,通过提升并行度而加速,相比于CPU端(85ms)加速了接近一倍左右。 5. 结论以上仅是一份简单的CUDA入门代码,看起来还算比较简单,不过继续深入肯定有更多的坑,期待后面有时间继续学习。 本文代码: GitHub - xcyuyuyu/My-First-CUDA-Code: The introduction to cuda, a simple and easy cuda project https://github.com/xcyuyuyu/My-First-CUDA-Code 参考文献 [1] An Even Easier Introduction to CUDA | NVIDIA Technical Blog(https://developer.nvidia.com/blog/even-easier-introduction-cuda/) 推荐阅读 西电IEEE Fellow团队出品!最新《Transformer视觉表征学习全面综述》来自谷歌、Meta工程师联合出品的模型部署秘籍(附pdf下载)如何做好科研?这份《科研阅读、写作与报告》PPT,手把手教你做科研最新 2022「深度学习视觉注意力 」研究概述,包括50种注意力机制和方法!【重磅】斯坦福李飞飞《注意力与Transformer》总结,84页ppt开放下载!分层级联Transformer!苏黎世联邦提出TransCNN: 显著降低了计算/空间复杂度!清华姚班教师劝退文:读博,你真的想好了吗?2021李宏毅老师最新40节机器学习课程!附课件+视频资料 欢迎大家加入DLer-计算机视觉技术交流群! 大家好,这是DLer-计算机视觉技术交流群,群里会第一时间发布计算机视觉相关技术的论文前沿论文以及工程项目实践等等,主要方向有:图像分类、Transformer、目标检测、目标跟踪、点云与语义分割、GAN、超分辨率、人脸检测与识别、动作行为与时空运动、模型压缩和量化剪枝、迁移学习、人体姿态估计等内容。 进群请备注:研究方向+学校/公司+昵称(如图像分类+上交+小明) ??长按识别,邀请您进群

上一篇:2024-10-26_Jellycat越来越 “癫“ 了 下一篇:2024-04-01_ICLR 2024 | 鸡生蛋蛋生鸡?再论生成数据能否帮助模型训练

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价