精简CUDA教程——CUDA Runtime API

2023-11-15

精简CUDA教程——CUDA Runtime API

tensorRT从零起步迈向高性能工业级部署(就业导向) 课程笔记,讲师讲的不错,可以去看原视频支持下。

Runtime API 概述

环境

在这里插入图片描述

  • 图中可以看到,Runtime API 是基于 Driver API 之上开发的一套 API。
  • 之前提到过 Driver API 基本都是 cu 开头的,而Runtime API 基本都是以 cuda 开头的。

Runtime API 的特点

  • Runtime API 与 Driver API 最大的区别是懒加载 ,即在真正执行功能时才自动完成对应的动作,即:
    • 第一个 Runtime API 调用时,会自动进行 cuInit 初始化,避免 Driver API 未初始化的错误;
    • 第一个需要 context 的 API 调用时,会创建 context 并进行 context 关联,和设置当前 context,调用 cuDevicePrimaryCtxRetain 实现;
    • 绝大部分 api 都需要 context,例如查询当前显卡名称、参数、内存分配释放等
  • CUDA Runtime 是封装了 CUDA Driver 的更高级别、更友好的 API
  • Runtime API 使用 cuDevicePrimaryCtxRetain 为每个设备设置 context,不再手动管理 context,并且不提供直接管理 context 的 API(可 Driver API 管理,通常不需要)
  • 可以更友好地执行核函数,.cpp 可以与 .cu 文件无缝对接
  • Runtime API 对应 cuda_runtime.hlibcudart.so
  • Runtime API 随 cudatoolkit 发布
  • 主要知识点是核函数的使用、线程束布局、内存模型、流的使用
  • 主要是为了实现归约求和、放射变换、矩阵乘法、模型后处理,就可以解决绝大部分问题

错误处理

类似于在介绍 Driver API 时的情况,我们同样提出 Runtime API 的错误处理方式:

#define checkRuntime(op)  __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){
        const char* err_name = cudaGetErrorName(code);
        const char* err_message = cudaGetErrorString(code);
        printf("runtime error %s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);
        return false;
    }
    return true;
}

内存模型 pinned memory

  • 内存模型是 CUDA 中很重要的知识点,主要理解 pinned_memory、global_memory、shared_memory 即可,其他的不太常用。
  • pinned_memory 属于 host memory,而 global_memory、shared_memory 属于 device memory。

下图是的 Device Memory 的分类

在这里插入图片描述

锁定性和性能

对于主机内存,即整个 host memory 而言,操作系统在逻辑上将其区分为两个大类:

  • pageable memory,可分页内存
  • page lock memory (pinned memory),页锁定内存/锁页内存

可以理解为 page lock memory 是酒店的 vip 房间,锁定给你一个人使用。而 pageable memory 是普通房间,在酒店房间不够的时候,选择性地将你的房间腾出来(交换到硬盘上)给其他人使用,这样就能容纳更多人了。造成房间很多的假象,代价是性能很低。pageable memory 就是常见的虚拟内存的特性。

基于前面的理解,我们总结如下:

  • 锁定性
    • pinned memory 具有锁定特性,是稳定不会被交换的,这很重要,相当于每次去这个房间都一定能找到你
    • pageable memory 没有锁定特性,对于第三方设备(如 GPU)去访问时,因为无法感知内存是否被交换,可能得到不到正确的数据,相当于每次去房间找你,说不定你的房间正好被交换了
    • 因此, GPU 可以直接访问 pinned memory 而不能访问 pageable memory
  • 性能
    • pageable memory 的性能比 pinned memory 差,因为我们的 pageable memory 很可能会被交换到硬盘上
    • pageable memory 策略能使用内存假象,比如实际只有 8G 内存却能使用 16G(借助 swap 交换),从而提高程序的运行数量
    • pinned memory 也不能太多,会导致操作系统整体性能变差(可同时运行的程序变少),而且 8G 内存最多就 8G 锁页内存。
数据传输到GPU

在这里插入图片描述

  • pinned memory 可以直接传送数据到 GPU

  • 而 pageable memory ,由于并不锁定,需要先传到 pinned memory。

关于内存其他几个点
  1. GPU 可以直接访问 pinned memory,称为 DMA (Direct Memort Access)

  2. 对于 GPU 访问而言,距离计算单元越近,效率越高,所以:

    SharedMemory > GlobalMemory > PinnedMemory

  3. 代码中,

    • new/malloc 分配的是 pageable memory
    • cudaMallocHost 分配的是 PinnedMemory
    • cudaMalloc 分配的是 GlobalMemory
  4. 尽量多用 PinnedMemory 储存 host 数据,或者显式处理 Host 到 Device 时,用 PinnedMemory 做缓存,都是提高性能的关键

流 stream

  • 流是一种基于 context 之上的任务管道(任务队列)抽象,一个 context 可以创建 n 个流
  • 流是异步控制的主要方式
  • nullptr 表示默认流,每个线程都有自己的默认流。
生活中的例子
同步(串行) 异步
在这里插入图片描述
在这里插入图片描述
  • 在这个例子中,男朋友的微信消息,就是任务队列,流的一种抽象
  • 女朋友发出指令之后,她可以做任何事情,无需等待指令执行完毕。即异步操作中,执行的代码加入流的队列之后,立即返回,不耽误时间。
  • 女朋友发的指令被送到流中排队,男朋友根据流的队列,顺序执行
  • 女朋友选择性,在需要的时候等待所有的执行结果
  • 新建一个流,就是新建一个男朋友,给他发指令就是发微信,可以新建很多个男朋友
  • 通过 cudaEvent 可以选择性等待任务队列中的部分任务是否就绪
注意

要十分注意,指令发出后,流队列中储存的是指令参数,不能在任务加入队列后立即释放参数指针,这会导致流队列执行该指令时指针失效而出错。应当在十分肯定流已经不需要这个指针之后,才进行修改或释放,否则会有非预期行为出现。

就比如,女朋友让男朋友去卖西瓜并转给了他钱,但是却在男朋友买瓜成功前将转账撤了回去,这时就无法知道男朋友在水果店会发生什么,比如会不会跟老板打起来之类的。因此,要保证买瓜行为顺利完成(行为符合预期),在买瓜成功前就不能动买瓜的钱。

核函数

简介
  • 核函数是 cuda 编程的关键

  • 通过 xxx.cu 创建一个 cudac 程序文件,并把 cu 文件交给 nvcc 编译,才能识别 cuda 语法;

  • __xxx__ 修饰

    • __global__ 表示为核函数,由 host 调用;
    • __device__ 表示设备函数,由 device 调用;
    • __host__ 表示主机函数,由 host 调用;
    • __shared__ 表示变量为共享变量。
    • 可能存在上述多个关键字修饰同一个函数,如 __device____host__ 修饰的函数,既可以设备上调用,也可以在主机上调用
  • host 调用核函数:

    function<<<gridDim, blockDim, sharedMemorySize, stream>>>(args, ...)
    

    gridDimblockDim 的变量类型为 dim3,是一个三维的值;

    function 函数总共启动的线程数目可以这样计算:n_threads = gridDim.x * gridDim.y * gridDim.z * blockDim.x * blockDim.y * blockDim.z

    详细请参考线程束的相关知识

  • 只有 __global__ 修饰的函数才可以用 <<< >>> 的方式调用s

  • 调用核函数是传值的,不能传引用,可以传递类,结构体等,核函数可以使模板

  • 核函数的返回值必须是 void

  • 核函数的执行是异步的,也就是立即返回的

  • 线程 layout 主要用到 blockDim、gridDim

  • 和函数内访问线程索引主要用到 threadIdx、blockIdx、blockDim、gridDim 这些内置变量

线程索引计算

共涉及四个变量:blockDimgridDimthreadIdxblockIdx ,其中前两者可以认为是形状,后两者可以认为是对应的索引。就像我们 PyTorch 中如果一个张量的形状为 ( 2 , 3 ) (2,3) (2,3) ,那么对应的,其两个维度上索引的取值范围就是: 0 − 1 , 0 − 2 0-1,0-2 01,02

在这里插入图片描述

线程索引 id 计算方法:左乘右加,如上图所示。

共享内存

  • __shared__ 关键字修饰

  • 共享内存因为更靠近计算单元,所以访问速度更快

  • 共享内存通常可以作为访问全局内存的缓存使用

  • 可以利用共享内存实现线程间的通信

  • 通常与 __syncthreads 同时出现,这个函数是同步 block 内的所有线程,全部执行到这一行才往下继续执行

    如:

    __shared__ int shared_value1;
    __shared__ int shared_value2;
    
    if (threadIdx.x == 0) {
      if (blockIdx.x == 0) {
        shared_value1 = 123;
        shared_value2 = 55;
      }
      else {
        shared_value1 = 331;
        shared_value2 = 8;
      }
      
      __syncthreads();
      printf("...")
    }
    

    其他 threadIdx.x 不为 0 的线程不会进到判断语句,但是会卡在 __syncthreads() ,等待 threadIdx.x 为 0 的线程设置好共享内存,再一起继续向下执行。

  • 共享内存使用方式:通常是在线程 id 为 0 的时候从 global memory 取值,然后 __syncthreads ,然后再使用

  • 动态共享内存与静态共享内存

    • 动态共享内存的声明需要加 extern 关键字,不需要指定数组大小,如:

      extern __shared__ char dynamic_shared_memory[];
      
    • 静态共享内存的声明需要指定数组大小,如:

      const size_t static_shared_memory_size = 6 * 1024; // 6KB
      __shared__ char static_shared_memory[static_shared_memory_size];
      

warp affine 实战

chapter: 1.6, caption: vector-add, description: 使用cuda核函数实现向量加法
chapter: 1.7, caption: shared-memory, description: 共享内存的操作
chapter: 1.8, caption: reduce-sum, description: 规约求和的实现,利用共享内存,高性能
chapter: 1.9, caption: atomic, description: 原子操作,实现动态数组的操作
chapter: 1.10, caption: warpaffine, description: 仿射变换双线性插值的实现,yolov5的预处理
chapter: 1.11, caption: cublas-gemm, description: 通用矩阵乘法的cuda核函数实现,以及cublasSgemm的调用
chapter: 1.12, caption: yolov5-postprocess, description: 使用cuda核函数实现yolov5的后处理案例

TODO

thrust

相当于 cuda 的 stl,但并不常用

错误处理

若核函数出错,由于它是异步的,立即执行 cudaPeekAtLastError 只会拿到对输入参数校验是否正确的状态,而不会拿到核函数是否正确执行的状态。

需要等待核函数真正执行完毕之后才知道当前核函数是否出错,一般通过设备同步或者流同步进行等待

错误分为可恢复和不可恢复两种

  • 可恢复
    • 参数配置错误,例如 block 越界(一般最大值是 1024),shared memory 超出大小范围(一般是 64KB)等
    • 通过 cudaGetlastError 可以获取错误代码,同时把当前状态恢复为success
    • 该种错误可以在调用核函数之后立即通过 cudaGetLastError / cudaPeekAtLastError 拿到
    • 该种错误在下一个函数调用时会覆盖
  • 不可恢复
    • 核函数执行错误,例如访问越界等
    • 该错误会传递到之后所有的 cuda 操作上
    • 错误状态通常需要等到核函数执行完毕才能够拿到,也就是有可能在后续的任何流程中突然异常(因为是异步的)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

精简CUDA教程——CUDA Runtime API 的相关文章

  • Tensorflow:如何在模型训练过程中实时监控 GPU 性能?

    我是 Ubuntu 和 GPU 新手 最近在我们的实验室中使用了一台配备 Ubuntu 16 04 和 4 个 NVIDIA 1080ti GPU 的新 PC 该机还拥有i7 16核处理器 我有一些基本问题 为 GPU 安装 Tensorf
  • cudaMallocManaged() 返回“不支持的操作”

    在 CUDA 6 0 中尝试托管内存给了我operation not supported打电话时cudaMallocManaged include cuda runtime h include
  • 有没有办法使用 GPU 调整图像大小?

    有没有办法使用可通过 NET 应用程序使用的 GPU 图形卡 调整图像大小 我正在寻找一种极其高效的方法来调整图像大小 并且听说 GPU 可以比 CPU 更快地完成此操作 使用 C 的 GDI 是否有已知的实现或示例代码使用 GPU 来调整
  • CUDA素数生成

    当数据大小增加超过 260k 时 我的 CUDA 程序停止工作 它不打印任何内容 有人能告诉我为什么会发生这种情况吗 这是我的第一个 CUDA 程序 如果我想要更大的素数 如何在 CUDA 上使用大于 long long int 的数据类型
  • MPI+CUDA 与纯 MPI 相比有何优势?

    加速应用程序的常用方法是使用 MPI 或更高级别的库 例如在幕后使用 MPI 的 PETSc 并行化应用程序 然而 现在每个人似乎都对使用 CUDA 来并行化他们的应用程序或使用 MPI 和 CUDA 的混合来解决更雄心勃勃 更大的问题感兴
  • 如何在 Visual Studio 2010 中设置 CUDA 编译器标志?

    经过坚持不懈的得到error identifier atomicAdd is undefined 我找到了编译的解决方案 arch sm 20旗帜 但是如何在 VS 2010 中传递这个编译器标志呢 我已经尝试过如下Project gt P
  • 仅使用 CUDA 进行奇异值计算

    我正在尝试使用新的cusolverDnSgesvdCUDA 7 0 用于计算奇异值的例程 完整代码如下 include cuda runtime h include device launch parameters h include
  • 运行时 API 应用程序中的 cuda 上下文创建和资源关联

    我想了解如何在 cuda 运行时 API 应用程序中创建 cuda 上下文并与内核关联 我知道这是由驱动程序 API 在幕后完成的 但我想了解一下创作的时间线 首先 我知道 cudaRegisterFatBinary 是第一个 cuda a
  • Nvcc 的版本与 CUDA 不同

    我安装了 cuda 7 但是当我点击 nvcc version 时 它打印出 6 5 我想在 GTX 960 卡上安装 Theano 库 但它需要 nvcc 7 0 我尝试重新安装cuda 但它没有更新nvcc 当我运行 apt get i
  • 无法在 CUDA 中执行设备内核

    我正在尝试在全局内核中调用设备内核 我的全局内核是矩阵乘法 我的设备内核正在查找乘积矩阵每列中的最大值和索引 以下是代码 device void MaxFunction float Pd float max int x threadIdx
  • NvCplGetThermalSettings 返回 false

    问题 您好 我正在尝试使用 Delphi 获取 nividia gtx 980 的 GPU 温度 我看过C 问题 他的解决方案是不使用nvcpl dll 我认为这不是正确的解决方案 因为 nivida 有完整的文档说明如何处理 API 见下
  • C# - 获取 GPU 的总使用百分比

    我正在向我的程序添加一些新功能 这些功能当前通过串行连接将 CPU 使用情况和 RAM 使用情况发送到 Arduino 请参阅this https create arduino cc projecthub thesahilsaluja cp
  • 加速Cuda程序

    要更改哪一部分来加速此代码 代码到底在做什么 global void mat Matrix a Matrix b int tempData new int 2 tempData 0 threadIdx x tempData 1 blockI
  • iOS 上的 OpenCV - GPU 使用情况?

    我正在尝试开发一个 iOS 应用程序 可以对来自相机的视频执行实时效果 就像 iPad 上的 Photobooth 一样 我熟悉 OpenCV 的 API 但如果大多数处理是在 CPU 上完成而不是在 GPU 上完成 我担心 iOS 上的性
  • Cuda 6.5 找不到 - libGLU。 (在 ubuntu 14.04 64 位上)

    我已经在我的ubuntu上安装了cuda 6 5 我的显卡是 GTX titan 当我想要制作 cuda 样本之一时 模拟 粒子 我收到这条消息 gt gt gt WARNING libGLU so not found refer to C
  • 使用 CUDA 进行逐元素向量乘法

    我已经在 CUDA 中构建了一个基本内核来执行逐元素两个复向量的向量 向量乘法 内核代码插入如下 multiplyElementwise 它工作正常 但由于我注意到其他看似简单的操作 如缩放向量 在 CUBLAS 或 CULA 等库中进行了
  • 如何在cmake中添加cuda源代码的定义

    我使用的是 Visual Studio 2013 Windows 10 CMake 3 5 1 一切都可以使用标准 C 正确编译 例如 CMakeLists txt project Test add definitions D WINDOW
  • 超出 CreateConstantBufferView 处虚拟地址的末尾

    我正在遵循 使用 DirectX12 进行游戏编程 ch 6 代码 但在 ID3DDevice CreateConstantBufferView 中 我发现 D3D12 错误 D3D12 错误 ID3D12Device CreateCons
  • NV_path_rendering替代方案[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我刚刚观看了 Siggraph 2012 的一个非常令人印象深刻的演示 http nvidia fullviewmedia com sig
  • CUDA - 将 CPU 变量传输到 GPU __constant__ 变量

    与 CUDA 的任何事情一样 最基本的事情有时也是最难的 所以 我只想将变量从 CPU 复制到 GPUconstant变量 我很难过 这就是我所拥有的 constant int contadorlinhasx d int main int

随机推荐

  • 为什么会出现Date类型传到数据库后面的时分秒变为0?

    前言 今天写代码的时候遇到了一个问题 现在来和大家分享一下 创建时间 crea time在数据中是Datetime类型 但是当我在实体类上使用 Temporal 注解来给这个日期类型进行转换时 如果要是用 Temporal Temporal
  • sam文件学习1

    1 FLAG说明 Each bit in the FLAG field is defined as 0x0001 p the read is paired in sequencing 0x0002 P the read is mapped
  • 创建RAM程序,验证程序在DDR上的运行

    前面我们验证了程序运行在SRAM中 也就是程序运行在L2 cache中 但是对于启动操作系统 这并不够 操作系统要必须要足够大的RAM 所以我们必须要程序运行在RAM中 也就是DDR上 环境 CodeWarriorDevelopmentSt
  • jwt的token自动续约_JWT token过期自动续期解决方案

    access token 用于一般的资源请求的token refresh token access token过期后用于刷续期的token 注意设置refresh token的过期时间需比access token的过期时间长 问题场景与解决
  • 【OpenCV图像处理】1.11 形态学操作 - 开操作、闭操作、顶帽、黑帽

    形态学操作 开操作 闭操作 顶帽 黑帽 开操作 open 先腐蚀后膨胀 d s t o p
  • qml使用数组技巧

    在qml中 使用JS数组 修改里面的值 不发出change信号 my array n value 为了得到数组属性通知 你必须使用下面这个技巧 var tmp my array tmp n value 你可以做多个更改 也可以push sp
  • 设计模式:外观模式

    有些人可能炒过股票 但其实大部分人都不太懂 这种没有足够了解证券知识的情况下做股票是很容易亏钱的 刚开始炒股肯定都会想 如果有个懂行的帮帮手就好 其实基金就是个好帮手 支付宝里就有许多的基金 它将投资者分散的资金集中起来 交由专业的经理人进
  • Redis——认识Redis

    简单介绍 Redis诞生于2009年 全称是Remote Dictionary Server 远程词典服务器 是一个基于内存的键值型NoSQL数据库 特征 键值 Key value 型 value支持多种不同数据结构 功能丰富 单线程 每个
  • Qt简单的异步操作实现方法

    Qt简单的异步操作实现方法 在实际应用中 经常会遇到一些耗时操作 导致了主线程的阻塞 这时候可以使用异步操作来避免阻塞 Qt的异步操作需要使用下面的库 include
  • python写邮箱验证工具_Python编写的Linux邮件发送工具

    之前有用过Linux自带的mail工具来定时发送邮件 但是要装mailx还有配mail rc 这还比较正常 关键是到了ubantu下这工具用起来真是操蛋 如果哪天其他的unix like操作系统也有需求 那就太麻烦了 所以我用自带的pyth
  • QT界面布局和设计

    一 设计 对功能和模块进行分析 然后设计对应的模块 将每个模块都用widget展示作为组件 工程结构示例 二 完成模块 代码分别设计各个组件 合适即可 三 主界面连接 主界面连接各个子模块 在这里插入代码片 include AutoFlaw
  • CAN总线详解及STM32的CAN通信编程指南

    对于CAN通信而言 本人之前也未接触了解过 由于实习的技术要求 因此也花费了一段时间对CAN通信进行学习 并且实现了基于STM32的CAN环回静默模式通信 因此写一遍比较详细的文章对该内容进行总结 本文的参考资料有STM32的中文参考手册
  • 【JAVA】Could not resolve all dependencies for configuration ‘:detachedConfiguration1‘

    build gradle 中添加 id net linguica maven settings version 0 5 plugins id org springframework boot version 2 3 3 RELEASE id
  • 【华为OD统一考试B卷

    华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一修改为OD统一考试 A卷 和OD统一考试 B卷 你收到的链接上面会标注A卷还是B卷 请注意 根据反馈 目前大部分收到的都是
  • centos7 安装jdk17

    默认情况下 yum 仓库中是没有jdk 17 的 只有jdk 11 所以我们不能直接用yum 安装 需要手动下载进行配置工作 下载文件 wget https download oracle com java 17 latest jdk 17
  • 网页与服务器数据库数据交互,网页与ACCESS数据库如何实现数据交互?

    1 打开access 单机菜单栏创建 选择表 单击列 选择下拉菜单中的字段类型 单机列名更改字段名称 2 添加完成后单击保存成test accdb 新建c 窗体程序 3 using System using System Collectio
  • 今日算法中数据结构知识练习 (7-19)【4】

    Date 2019 07 19 1 两个指针 P 和 Q 分别指向单链表的两个元素 P 所指元素是 Q 所指元素前驱的条件是 P gt next Q 2 串是一种特殊的线性表 其特殊性体现在 数组元素是一个字符 3 下列文件中属于逻辑结构文
  • uniapp onHide()和onUnload()的使用

    小程序onHide 和onUnload onHide 触发的场景 导航页1 gt gt 导航页2 会触发导航页1 onHide 导航页 gt gt 子页面 会触发导航页 onHide 子页面1 gt gt 子页面2 会触发子页面1 onHi
  • linux查询mysql内存使用率_MySQL内存使用率无限增长

    背景 收到内存报警的信息以后 从监控中发现MySQL服务器的内存使用率在不断的增长 附图 虽然进行了重启 但是内存占用率依然会不停的增长 大约在半个月左右的时间内又把内存消耗完毕 场景 未搭建场景 数据库版本 5 7 12 分析 PS 时间
  • 精简CUDA教程——CUDA Runtime API

    精简CUDA教程 CUDA Runtime API tensorRT从零起步迈向高性能工业级部署 就业导向 课程笔记 讲师讲的不错 可以去看原视频支持下 Runtime API 概述 环境 图中可以看到 Runtime API 是基于 Dr