深度学习模型部署(十二)CUDA编程-绪

请添加图片描述

CUDA 运行时 API 与 CUDA 驱动 API 速度没有差别,实际中使用运行时 API 较多,运行时 API 是在驱动 API 上的一层封装。​

CUDA 是什么?​

CUDA(Compute Unified Device Architecture) 是 nvidia 推出的一个通用并行技术架构,用它来进行 GPU 编程。CUDA 本身并不是一门语言,而是一个 GPU 编程模型,是对 C++,Python 这种常见 CPU 编程语言的一个补充。​

为什么用 GPU 编程要用 CUDA?​

因为 GPU 的控制硬件少,所以编程模型要求非常严格,最早期 GPU 的唯一交互方式是通过 OpenGL 和 DirectX 这些图形 API,​

基本上所有的编程语言都是在 CPU 上运行的,所以催生了 CUDA 编程框架,为了更方便的与 GPU 交互和调用 GPU 的资源.​

请添加图片描述

CUDA编译运行流程

1、CUDA代码文件后缀.cu,使用nvcc进行编译
nvcc是nvidia基于LLVM开发的专门用于编译cuda代码的编译器,cuda代码有一套完整的工具链,称为cuda-toolkit,包括nvcc编译器,cuda-gdb调试工具等。
2、CUDA中的代码执行设备有两种,一种是device,一种是host,CPU被称为host,普通代码都是在host上执行,GPU被称为device,在device上执行的代码需要添加__global__ 或者__device__前缀
具体CUDA不同前缀意义见
3、CUDA中device使用的是显存,所以在device上执行的函数只能传入device上定义的变量,具体方法为定义好变量,然后使用cudaMalloc函数在显存中给变量分配空间,再使用cudaMemcpy将变量拷贝到显存中(cudaMemcpy是内存拷贝函数,可以根据给的参数将device中的memory拷贝到host中,也可以反过来拷贝),使用完后要使用cudaFree进行释放
4、通过__global__前缀定义的函数执行时需要设置执行的block数量和线程数
5、在host函数中进行完cuda调用后,需要使用cudaDeviceSynchronize()函数,因为启动内核是一个异步操作,只要发布了内核启动命令,不等内核执行完成,控制权就会立刻返回给调用内核的CPU线程。

CUDA kernel和线程管理

在CUDA中,函数称为kernel,每个kernel都有一个前缀,不同的前缀代表了kernel不同的运行要求。

#include<stdio.h>
//在GPU上执行的kernel中不允许使用C++的标准库 iostream
// warning #20096-D: address of a host variable "std::cout" cannot be directly taken in a device function
__global__ void hello_from_gpu()
{
    printf("hello world from gpu\n");
    //compute capacity 2.0以后才支持printf,也就是GeForce 830M以后的GPU
}

int main(void)
{
    hello_from_gpu<<<4,4>>>();
    cudaDeviceSynchronize();

    return 0;
}

<<<>>>是cuda调用kernel时的语法,<<<numBlocks, threadsPerBlock>>>意思为调用numBlocks个block,每个block中threadsPerBlock个线程。

函数前缀

  1. device
    使用 device 限定符声明的函数具有以下特征:
  • 在设备上执行;
  • 仅可通过设备调用。
  1. global
    使用 global 限定符可将函数声明为内核。此类函数:
  • 在设备上执行;
  • 仅可通过主机调用。
  1. host
    使用 host 限定符声明的函数具有以下特征:
  • 在主机上执行;
  • 仅可通过主机调用。
    仅使用 host 限定符声明函数等同于不使用限定符声明函数,这两种情况下,函数都将仅为主机进行编译。
    函数前缀的一些限制
    deviceglobal 函数不支持递归。
    deviceglobal 函数的函数体内无法声明静态变量。
    deviceglobal 函数不得有数量可变的参数。
    device 函数的地址无法获取,但支持 global 函数的函数指针。
    globalhost 限定符无法一起使用。
    global 函数的返回类型必须为空。
    global 函数的任何调用都必须按规定指定其执行配置。
    global 函数的调用是异步的,也就是说它会在设备执行完成之前返回。
    global 函数参数将同时通过共享存储器传递给设备,且限制为 256 字节。
    线程管理
    CUDA有一套专门用于线程管理的机制,一个kernel调用时的配置<<<numBlocks, threadsPerBlock>>>中的numBlocks和threadPerBlock可以是int变量,也可以是线程管理的结构体,结构体更为常见。
#include<stdio.h>
#include<cuda_runtime.h>

__global__ void build_in_variables(void)
{
    // build-in variables
    // blockDim:等同于threadsPerBlock
    // gridDim:等同于numBlocks
    // blockIdx:一个block在grid中的id
    // threadIdx:一个thread在block中的id

    const int blockId = blockIdx.x + blockIdx.y * gridDim.x;
    const int threadId = threadIdx.x + blockDim.x * threadIdx.y;

    printf("blockIdx=(%d,%d) \n",blockIdx.x,blockIdx.y);
    printf("threadIdx=(%d,%d) \n",threadIdx.x,threadIdx.y);
    printf("blockid=:%d,threadId=%d \n",blockId,threadId);

}

int main(void)
{
    printf("*****device message*******\n");

    int dev=0;
    cudaDeviceProp deviceProp;
    cudaGetDeviceProperties(&deviceProp,dev);
    printf("Using Device %d:%s\n",dev,deviceProp.name);
    printf("Device %d has compute capability %d.%d.\n",dev,deviceProp.major,deviceProp.minor);
    printf("Device %d has %d multi-processors.\n",dev,deviceProp.multiProcessorCount);
    printf("Device %d has %zu byte total global memory.\n",dev,deviceProp.totalGlobalMem);
    printf("Device %d has %zu byte total constant memory.\n",dev,deviceProp.totalConstMem);
    printf("Device %d has %zu byte shared memory per block.\n",dev,deviceProp.sharedMemPerBlock);
    printf("Device %d has %d total registers per block.\n",dev,deviceProp.regsPerBlock);
    printf("Device %d has %d max threads per block.\n",dev,deviceProp.maxThreadsPerBlock);
    printf("Device %d has %d max threads dimensions.\n",dev,deviceProp.maxThreadsDim[0]);
    printf("Device %d has %u max grid size.\n",dev,deviceProp.maxGridSize[0]);
    printf("Device %d has %d warp size.\n",dev,deviceProp.warpSize);
    printf("Device %d has %d clock rate.\n",dev,deviceProp.clockRate);
    printf("Device %d has %d max threads per multi-processor.\n",dev,deviceProp.maxThreadsPerMultiProcessor);
    

    dim3 numBlocks(2,2);
    // 2*2个block per grid
    // dim3,是一个包含xyz三个无符号整型数的结构体,默认值为1
    //三个维度,x变化最快,然后是y,最后是z
    dim3 threadsPerBlock(2,2);
    // 2*2个thread per block
    build_in_variables<<<numBlocks, threadsPerBlock>>>();
    cudaDeviceReset();
    return 0;
}

在这里插入图片描述

在计算能力9.0以前的架构,thread的Hierarchy是二维的,只有两个层次,一个grid,一个block。在9.0以后的架构,thread的Hierarchy是三维的,新引入了一个可选层次:Cluster集群,每个Cluster中的block可以确保是在同一个GPC(GPU Processing Cluster)GPU集群中运行的。

#include<stdio.h>

__global__ void __cluster_dims__(2,1,1) hello_from_gpu()
{
    printf("Hello World from GPU!\n");
}

int main()
{
    hello_from_gpu<<<1,1>>>();
    cudaDeviceSynchronize();
    return 0;
}

内存管理

内存分为device memory和host memory,二者之间通过cudaMemcpy来进行管理。
[图片]

GPU每个线程有自己单独的寄存器和内存,同一个block中有所有thread都能访问的shared memory,
在有cluster的架构中,同一个cluster中的block的shared memory组成了distributed shared memory,可以相互访问。除此之外还有专门的只读内存,用于存放texture(纹理)

#include <stdio.h>

__global__ void sharedMemoryExample(int* input)
{
    // Define shared memory array
    __shared__ int sharedArray[256];

    // Get the thread index
    int tid = threadIdx.x;

    // Load data from global memory to shared memory
    sharedArray[tid] = input[tid];

    // Synchronize threads to ensure all data is loaded
    __syncthreads();

    // Perform some computation using shared memory data
    sharedArray[tid] = sharedArray[tid] * 2;

    // Synchronize threads again before writing back to global memory
    __syncthreads();

    // Write the result back to global memory
    input[tid] = sharedArray[tid];
}

int main()
{
    // Define input data
    int input[256];

    // Initialize input data
    for (int i = 0; i < 256; i++)
    {
        input[i] = i;
    }

    // Allocate memory on the GPU
    int* d_input;
    cudaMalloc((void**)&d_input, sizeof(int) * 256);

    // Copy input data from host to device
    cudaMemcpy(d_input, input, sizeof(int) * 256, cudaMemcpyHostToDevice);

    // Launch the kernel
    sharedMemoryExample<<<1, 256>>>(d_input);

    // Copy the result back from device to host
    cudaMemcpy(input, d_input, sizeof(int) * 256, cudaMemcpyDeviceToHost);

    // Print the result
    for (int i = 0; i < 256; i++)
    {
        printf("%d ", input[i]);
    }

    // Free memory on the GPU
    cudaFree(d_input);

    return 0;
}

变量前缀:

1.device
device 限定符声明位于设备上的变量。
在接下来的三节中介绍的其他类型限定符中,最多只能有一种可与 device 限定符一起使用,以更具体地指定变量属于哪个存储器空间。如果未出现其他任何限定符,则变量具有以下特征:

  • 位于全局存储器空间中;
  • 与应用程序具有相同的生命周期;
    可通过网格内的所有线程访问,也可通过运行时库从主机访问。
    2.constant
    constant 限定符可选择与 device 限定符一起使用,所声明的变量具有以下特征:
  • 位于固定存储器空间中;
  • 与应用程序具有相同的生命周期;
    可通过网格内的所有线程访问,也可通过运行时库从主机访问。
    3.shared
    shared 限定符可选择与 device 限定符一起使用,所声明的变量具有以下特征:
  • 位于线程块的共享存储器空间中;
  • 与块具有相同的生命周期;
  • 尽可通过块内的所有线程访问。
    只有在 syncthreads()(参见第 4.4.2 节)的执行写入之后,才能保证共享变量对其他线程可见。除非变量被声明为瞬时变量,否则只要之前的语句完成,编译器即可随意优化共享存储器的读写操作。
    限制:
    不允许为在主机上执行的函数内的 struct 和 union 成员、形参和局部变量使用这些限定符。
    shared 和 constant 变量具有隐含的静态存储。
    devicesharedconstant 变量无法使用 extern 关键字定义为外部变量。
    deviceconstant 变量仅允许在文件作用域内使用。
    不可为设备或从设备指派 constant 变量,仅可通过主机运行时函数从主机指派(参见第 4.5.2.3 节和第 4.5.3.6 节)。
    shared 变量的声明中不可包含初始化。
    访问速度顺序为:register>shared>constant>local>device
    下面是具体的一个应用:
    将共享存储器中的变量声明为外部数组时,例如:
extern __shared__ float shared[];

数组的大小将在启动时确定(参见第 4.2.3 节)。所有变量均以这种形式声明,在存储器中的同一地址开始,因此数组中的变量布局必须通过偏移显式管理。
例如:

// 如果一名用户希望在动态分配的共享存储器内获得与以下代码对应的内容:
// short array0[128];
// float array1[64];
// int array2[256];
// 则应通过以下方法声明和初始化数组:
extern __shared__ char array[];
__device__ void func() // device or global function
{
    short* array0 = (short*)array;
     float* array1 = (float*)&array0[128];
     int* array2 = (int*)&array1[64];
}

在设备代码中声明、不带任何限定符的自动变量通常位于寄存器中。但在某些情况下,编译器可能选择将其置于本地存储器中。
只要编译器能够确定在设备上执行的代码中的指针指向的是共享存储器空间还是全局存储器空间,此类指针即受支持,否则将仅限于指向在全局存储器空间中分配或声明的存储器。
通过获取 devicesharedconstant 变量的地址而获得的地址仅可在设备代码中使用。通过 cudaGetSymbolAddress() 获取的 deviceconstant 变量的地址仅可在主机代码中使用。

对 global 函数进行配置

global 函数的任何调用都必须指定该调用的执行配置。
执行配置定义将用于在该设备上执行函数的网格和块的维度,以及相关的流。可通过在函数名称和括号参数列表之间插入 <<<Dg, Db, Ns, s>>> 形式的表达式来指定,其中:
Dg 的类型为 dim3,指定网格的维度和大小,Dg.x * Dg.y 等于所启动的块数量,Dg.z 无用;
Db 的类型为 dim3,指定各块的维度和大小,Db.x * Db.y * Db.z 等于各块的线程数量;
Ns 的类型为 size_t,指定各块为此调用动态分配的共享存储器(除静态分配的存储器之外),这些动态分配的存储器可供声明为外部数组的其他任何变量使用,Ns 是一个可选参数,默认值为 0;
S 的类型为 cudaStream_t,指定相关流;S 是一个可选参数,默认值为 0。
举例来说,

//一个函数的声明如下:
__global__ void Func(float* parameter);
//必须通过如下方法来调用此函数:
Func<<<Dg, Db, Ns>>>(parameter);

执行配置的参数将在实际函数参数之前被评估,与函数参数相同,通过共享存储器同时传递给设备。

如果 Dg 或 Db 大于设备允许的最大大小,或 Ns 大于设备上可用的共享存储器最大值,或者小于静态分配、函数参数和执行配置所需的共享存储器数量,则函数将失败。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于冠豪猪优化器(CPO)的无人机路径规划

该优化算法是2024年新发表的一篇SCI一区top论文具有良好的实际应用和改进意义。一键运行main函数代码自动保存高质量图片 1、冠豪猪优化器 摘要&#xff1a;受冠豪猪(crest Porcupine, CP)的各种防御行为启发&#xff0c;提出了一种新的基于自然启发的元启发式算法——冠豪猪…

视觉轮速滤波融合1讲:理论推导

视觉轮速滤波融合理论推导 文章目录 视觉轮速滤波融合理论推导1 坐标系2 轮速计2.1 运动学模型2.2 外参 3 状态和协方差矩阵3.1 状态3.2 协方差矩阵 4 Wheel Propagation4.1 连续运动学4.2 离散积分4.2.1 状态均值递推4.2.2 协方差递推 5 Visual update5.1 视觉残差与雅可比5.2…

蓝桥杯2023年第十四届省赛真题-买瓜|DFS+剪枝

题目链接&#xff1a; 0买瓜 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-买瓜 - C语言网 (dotcpp.com) &#xff08;蓝桥官网的数据要求会高一些&#xff09; 说明&#xff1a; 这道题可以分析出&#xff1a;对一个瓜有三种选择&#xff1a; 不拿&#xff0c…

Vue3基础笔记(2)事件

一.事件处理 1.内联事件处理器 <button v-on:click"count">count1</button> 直接将事件以表达式的方式书写~ 每次单击可以完成自增1的操作~ 2.方法事件处理器 <button click"addcount(啦啦啦~)">count2</button> 如上&…

每日必学Linux命令:mv命令

mv命令是move的缩写&#xff0c;可以用来移动文件或者将文件改名&#xff08;move (rename) files&#xff09;&#xff0c;是Linux系统下常用的命令&#xff0c;经常用来备份文件或者目录。 一&#xff0e;命令格式&#xff1a; mv [选项] 源文件或目录 目标文件或目录二&am…

Open WebUI大模型对话平台-适配Ollama

什么是Open WebUI Open WebUI是一种可扩展、功能丰富、用户友好的大模型对话平台&#xff0c;旨在完全离线运行。它支持各种LLM运行程序&#xff0c;包括与Ollama和Openai兼容的API。 功能 直观的界面:我们的聊天界面灵感来自ChatGPT&#xff0c;确保了用户友好的体验。响应…

轻松掌握C语言中的sqrt函数,快速计算平方根的魔法秘诀

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

第1章 实时3D渲染流水线

前言 本书所剖析的Unity 3D内置着色器代码版本是2017.2.0f3&#xff0c;读者可以从Unity 3D官网下载这些着色器代码。这些代码以名为builtin_shaders-2017.2.0f3.zip的压缩包的形式提供&#xff0c;解压缩后&#xff0c;内有4个目录和1个license.txt文件。 目录CGIncludes存放了…

苍穹外卖项目-01(开发流程,介绍,开发环境搭建,nginx反向代理,Swagger)

目录 一、软件开发整体介绍 1. 软件开发流程 1 第1阶段: 需求分析 2 第2阶段: 设计 3 第3阶段: 编码 4 第4阶段: 测试 5 第5阶段: 上线运维 2. 角色分工 3. 软件环境 1 开发环境(development) 2 测试环境(testing) 3 生产环境(production) 二、苍穹外卖项目介绍 …

Docker搭建LNMP环境实战(05):CentOS环境安装Docker-CE

前面几篇文章讲了那么多似乎和Docker无关的实战操作&#xff0c;本篇总算开始说到Docker了。 1、关于Docker 1.1、什么是Docker Docker概念就是大概了解一下就可以&#xff0c;还是引用一下百度百科吧&#xff1a; Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以…

SE注意力模块学习笔记《Squeeze-and-Excitation Networks》

Squeeze-and-Excitation Networks 摘要引言什么是全局平均池化&#xff1f; 相关工作Deep architectures Squeeze-and-Excitation Blocks3.1. Squeeze: Global Information Embedding3.2. Excitation: Adaptive Recalibration3.3. Exemplars: SE-Inception and SE-ResNet 5. Im…

百科词条编辑必备指南,让你轻松上手创建

1.注册账号&#xff1a;首先&#xff0c;你需要注册一个百科平台的账号。例如&#xff0c;对于百度百科&#xff0c;你需要有一个百度账号。 搜索词条&#xff1a;在百科全书平台上搜索您想要编辑的词条。如果词条已经存在&#xff0c;可以直接编辑&#xff1b;如果词条不存在&…

(已解决)vue3使用富文本出现样式乱码

我在copy代码到项目里面时候发现我的富文本乱码了 找了一圈不知道是哪里vue3不适配还是怎么&#xff0c;后来发现main.js还需要引入 import VueQuillEditor from vue-quill-editor // require styles 引入样式 import quill/dist/quill.core.css import quill/dist/quill.snow…

计算机组成原理(超详解!!) 第三节 运算器(浮点加减乘)

1.浮点加法、减法运算 操作过程 1.操作数检查 如果能够判断有一个操作数为0&#xff0c;则没必要再进行后续一系列操作&#xff0c;以节省运算时间。 2.完成浮点加减运算的操作 (1) 比较阶码大小并完成对阶 使二数阶码相同&#xff08;即小数点位置对齐&#xff09;…

力扣Lc21--- 389. 找不同(java版)-2024年3月26日

1.题目描述 2.知识点 &#xff08;1&#xff09;在这段代码中&#xff1a; // 统计字符串s中每个字符的出现次数for (int i 0; i < s.length(); i) {count[s.charAt(i) - a];}对于字符串s “abcd”&#xff1a; 当 i 0&#xff0c;s.charAt(i) ‘a’&#xff0c;ASCII…

牛客小白月赛89(A,B,C,D,E,F)

比赛链接 官方视频讲解&#xff08;个人觉得讲的还是不错的&#xff09; 这把BC偏难&#xff0c;差点就不想做了&#xff0c;对小白杀伤力比较大。后面的题还算正常点。 A 伊甸之花 思路&#xff1a; 发现如果这个序列中最大值不为 k k k&#xff0c;我们可以把序列所有数…

2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年道路运输企业主要负责人证模拟考试题库及道路运输企业主要负责人理论考试试题是由安全生产模拟考试一点通提供&#xff0c;道路运输企业主要负责人证模拟考试题库是根据道路运输企业主要负责人最新版教材&#…

数据结构进阶篇 之 【二叉树】详细概念讲解(带你认识何为二叉树及其性质)

有朋自远方来&#xff0c;必先苦其心志&#xff0c;劳其筋骨&#xff0c;饿其体肤&#xff0c;空乏其身&#xff0c;鞭数十&#xff0c;驱之别院 一、二叉树 1、二叉树的概念 1.1 二叉树中组分构成名词概念 1.2 二叉树的结构概念 1.3 特殊的二叉树 2、二叉树的存储结构 …

沪漂8年回郑州三年如何走上创业之路

大家好&#xff0c;我是大牛&#xff0c;目前人在郑州。 现在标签是&#xff1a; 创业者&#x1f697;&#x1f438; (注册有自己的公司&#xff0c;主要是为了自己的产品和接外包项目)独立开发者&#x1f468;&#x1f3fb;&#x1f4bb; (有自己的小项目)数字游民&…

SpringDoc 注解

列举几个常用的 1. Tag 用于说明或定义的标签。一般作用于控制层 2.Operation(summary "这是新增方法") 描述 API 操作的元数据信息。常用于 controller 层的方法上 ​ 3.Parameter 用于描述 API 操作中的参数 ​ 4.Operation Parameters ​ 5.Schema用于…
最新文章