当前位置: 首页 > backend >正文

cuDNN深度解析:实战演练

文章目录

    • 0. 前言
    • 1. 初始化句柄
    • 2. 设置描述符
    • 3. 配置参数
    • 4. 执行操作
    • 5. 释放资源
    • 5. 总结
    • 6. 附录:完整操作代码

0. 前言

📣按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

前文介绍了cuDNN的架构与功能,本文将通过实例学习cuDNN的使用,让你对cuDNN的理解更上一层楼。

1. 初始化句柄

在使用cuDNN进行任何操作之前,需要初始化cuDNN上下文。可以使用cudnnCreate()函数来创建一个cuDNN上下文句柄,后续的所有cuDNN操作都将基于这个句柄进行。

cudnnHandle_t cudnn;
cudnnCreate(&cudnn);

句柄(cudnnHandle_t)是cuDNN中一个非常重要的概念,它本质上是一个指向cuDNN内部状态数据结构的指针,句柄的作用类似于 “会话 ID”,cuDNN 通过它识别并管理当前的计算环境,确保所有操作在正确的上下文(如特定 GPU 设备、资源分配状态等)中执行。

cudnncudnnHandle_t类型的变量,&cudnn获取了cudnn变量的内存地址。cudnnCreate函数接收这个地址后,就能在函数内部将创建的上下文句柄直接赋值到cudnn变量所在的内存空间,这样在函数调用结束后,外部的cudnn变量就保存了有效的上下文句柄,供后续操作使用 。

  • 作用:句柄就像是一个会话标识,cuDNN的所有函数都需要通过句柄来关联到特定的上下文。这个上下文包含了cuDNN库的各种状态信息,如已分配的资源、当前的配置参数等。通过句柄,cuDNN能够区分不同的计算任务,确保各个任务之间的状态不相互干扰。
  • 创建与销毁:句柄通过cudnnCreate()函数创建,在使用完成后,必须通过cudnnDestroy()函数进行销毁,以释放句柄所占用的资源,避免内存泄漏。
  • 线程安全性:cuDNN句柄不是线程安全的,每个线程应该使用独立的句柄。如果在多线程环境中使用同一个句柄,可能会导致不可预测的错误。因此,在多线程编程时,需要为每个线程单独创建和管理句柄。
  • 与CUDA上下文的关系:cuDNN句柄通常与一个CUDA上下文相关联(默认情况下与当前的CUDA上下文关联)。CUDA上下文是管理GPU资源的基础,cuDNN句柄通过关联的CUDA上下文来访问GPU硬件资源,进行数据传输和计算操作。

2. 设置描述符

描述符是cuDNN中用于描述各种对象属性的数据结构,它为cuDNN函数提供了执行操作所需的元数据信息。cuDNN中存在多种类型的描述符,如张量描述符(cudnnTensorDescriptor_t)、卷积描述符(cudnnConvolutionDescriptor_t)、池化描述符(cudnnPoolingDescriptor_t)等,不同类型的描述符用于描述不同操作或数据的属性。

  • 张量描述符(cudnnTensorDescriptor_t:用于描述张量的属性,包括张量的维度(如批量大小、通道数、高度、宽度等)、数据类型(如CUDNN_DATA_FLOAT表示32位浮点数)、张量布局(如CUDNN_TENSOR_NCHW表示批量-通道-高度-宽度的布局)。通过cudnnCreateTensorDescriptor()创建,cudnnSetTensor4dDescriptor()等函数设置属性,cudnnDestroyTensorDescriptor()销毁。

  • 卷积描述符(cudnnConvolutionDescriptor_t:用于描述卷积操作的参数,如填充大小(pad_h、pad_w)、步长(stride_h、stride_w)、dilation系数(用于空洞卷积)、卷积模式(如CUDNN_CROSS_CORRELATION表示互相关模式,在深度学习中通常等同于卷积操作)、数据类型等。由cudnnCreateConvolutionDescriptor()创建,cudnnSetConvolution2dDescriptor()设置属性,cudnnDestroyConvolutionDescriptor()销毁。

  • 池化描述符(cudnnPoolingDescriptor_t:用于描述池化操作的参数,如池化模式(最大池化、平均池化等)、池化窗口大小、步长、填充大小等。通过cudnnCreatePoolingDescriptor()创建,cudnnSetPooling2dDescriptor()设置属性,cudnnDestroyPoolingDescriptor()销毁。

例如设置张量描述符:

cudnnTensorDescriptor_t TensorDesc;
cudnnCreateTensorDescriptor(&TensorDesc);

3. 配置参数

对于不同的操作(如卷积、池化等),cuDNN提供了多种算法可供选择。可以使用相应的函数(如cudnnGetConvolutionForwardAlgorithm())获取合适的算法,并设置相关的参数,例如卷积核描述符、卷积操作的步长、填充等:

cudnnConvolutionDescriptor_t convDesc;
cudnnCreateConvolutionDescriptor(&convDesc);
cudnnSetConvolution2dDescriptor(convDesc, pad_h, pad_w, stride_h, stride_w, 1, 1, 

4. 执行操作

设置好相关的描述符和算法后,就可以调用cuDNN提供的函数执行具体的操作了,如卷积操作可以使用cudnnConvolutionForward()函数。

cudnnConvolutionForward(cudnn, &alpha, inputTensorDesc, inputData, filterDesc, filterData, convDesc, algo, workspace, workspaceSize, &beta, outputTensorDesc, outputData);

5. 释放资源

cuDNN资源释放需调用特定函数销毁创建的描述符和句柄,避免内存泄漏。如用cudnnDestroyDescriptor销毁张量、卷积等描述符,cudnnDestroy销毁cuDNN上下文句柄。释放顺序通常与创建相反,先释放各类描述符,最后销毁句柄,确保资源完全回收,尤其在循环或多次创建资源场景中需严格执行。例如释放张量描述符:

cudnnDestroyTensorDescriptor(TensorDesc); 

5. 总结

通过本文的介绍,我们了解了cuDNN的基本概念、在软件工具链中的位置、核心功能、安装配置方法、使用流程、性能优化策略以及常见问题的解决方案。

在实际应用中,只有不断实践和探索,才能更好地掌握cuDNN的使用技巧,充分发挥其性能优势,加速深度学习模型的训练和推理过程。希望本文能够为你学习和使用cuDNN提供有力的帮助,让你在深度学习的道路上更进一步。

6. 附录:完整操作代码

#include<cudnn.h>
#include<stdio.h>int main() {// 1.初始化cuDNN上下文,创建句柄cudnnHandle_t cudnn;cudnnCreate(&cudnn);// 2.设置各种描述符cudnnTensorDescriptor_t inDesc, outDesc;  //输入输出张量描述符cudnnCreateTensorDescriptor(&inDesc);cudnnCreateTensorDescriptor(&outDesc);cudnnFilterDescriptor_t filterDesc;   //卷积核描述符cudnnCreateFilterDescriptor(&filterDesc);cudnnConvolutionDescriptor_t convDesc;  //卷积描述符cudnnCreateConvolutionDescriptor(&convDesc);//3.设置参数//配置输入(4D)张量描述符printf("input tensor dimension=2(batch)*3(channel)*28(H)*28(W)\n");cudnnSetTensor4dDescriptor(inDesc,                 // 张量描述符CUDNN_TENSOR_NCHW,      // 数据布局:NCHW 格式CUDNN_DATA_FLOAT,       // 数据类型:32 位浮点数2,                      // Batch Size = 2(2 个样本)3,                      // Channels = 3(RGB 三通道)28,                     // Height = 2828                      // Width = 28);//配置卷积核printf("Convolution filter dimension=6(out_channel)*3(in_channel)*3(H)*3(W)\n");cudnnSetFilter4dDescriptor(filterDesc,               // 过滤器描述符CUDNN_DATA_FLOAT,         // 数据类型:32 位浮点数CUDNN_TENSOR_NCHW,        // 张量的数据格式,NCHW表示(batch, channel, height, width)6,                        // 过滤器的数量(输出通道数),即卷积后特征图的通道数3,                        // 每个过滤器的输入通道数,需与输入特征图的通道数匹配3,                        // 过滤器的高度(卷积核的高)3                         // 过滤器的宽度(卷积核的宽));// 配置(2D)卷积操作的描述符cudnnSetConvolution2dDescriptor(convDesc,                  // 指向要初始化的卷积描述符1, 1,                      // 输入特征图的填充(padding)大小,分别为高度和宽度方向的填充1, 1,                      // 卷积步长(stride),分别为高度和宽度方向的步长1, 1,                      //  dilation(扩张)系数,分别为高度和宽度方向,1表示不扩张CUDNN_CONVOLUTION,         // 卷积模式,此处为标准卷积CUDNN_DATA_FLOAT           // 卷积运算中使用的数据类型,这里为32位浮点数);//自动计算输出维度int outN, outC, outH, outW;cudnnGetConvolution2dForwardOutputDim(convDesc, inDesc, filterDesc, &outN, &outC, &outH, &outW);printf("output tensor dimension=%d(batch)*%d(channel)*%d(H)*%d(W)", outN, outC, outH, outW);//配置输出(4D)张量描述符cudnnSetTensor4dDescriptor(outDesc, CUDNN_TENSOR_NCHW, CUDNN_DATA_FLOAT, outN, outC, outH, outW);//4.选择算法执行操作float alpha = 1.0f, beta = 0.0f;cudnnConvolutionForward(cudnn,                  // cuDNN上下文句柄,用于管理cuDNN库的状态&alpha,                 // 输入张量的缩放因子指针,通常为1.0finDesc,                 // 输入张量的描述符,定义输入数据的格式和维度NULL,                   // 输入张量的数据指针,此处为NULL表示示例中未提供实际数据,仅演示流程。filterDesc,             // 卷积核(过滤器)的描述符,定义卷积核的属性NULL,                   // 卷积核的数据指针,此处为NULL表示示例中未提供实际数据,仅演示流程convDesc,               // 卷积操作的描述符,定义卷积的各种参数(步长、填充等)CUDNN_CONVOLUTION_FWD_ALGO_GEMM,  // 前向卷积使用的算法,此处选择GEMM-based算法NULL,                   // 工作空间指针,用于算法执行过程中的临时存储,NULL表示不使用0,                      // 工作空间的大小(字节),0表示不使用工作空间&beta,                  // 输出张量的缩放因子指针,通常为0.0f表示覆盖输出而非累加outDesc,                // 输出张量的描述符,定义输出数据的格式和维度NULL                    // 输出张量的数据指针,此处为NULL表示示例中未提供实际存储地址,仅演示流程);//5.释放资源cudnnDestroyConvolutionDescriptor(convDesc);cudnnDestroyTensorDescriptor(inDesc);cudnnDestroyTensorDescriptor(outDesc);cudnnDestroyFilterDescriptor(filterDesc);cudnnDestroy(cudnn);return 0;
}

输出为:

在这里插入图片描述

人生苦短,我还是想用Python。😠

http://www.xdnf.cn/news/19644.html

相关文章:

  • Electron 菜单与托盘:构建用户友好的界面元素
  • 9月2日
  • 深入分析 json2(新)与标准的 jsonrpc的区别
  • zephyr设备树的硬件描述转换为c语言
  • Hash 算法 SHA-1、SHA-256、SHA-384、SHA-512 对比
  • SpringBoot3 + Netty + Vue3 实现消息推送(最新)
  • 食品分类案例
  • 码住!辉芒微MCU型号规则详细解析
  • Kafka 架构详解
  • 动子注册操作【2025.9.2学习记录】
  • MVP架构深层剖析-从六大设计原则的实现角度到用依赖注入深度解耦
  • Elasticsearch 核心知识与常见问题解析
  • MCU上跑AI—实时目标检测算法探索
  • 【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
  • 空间不足将docker挂载到其他位置
  • 03_网关ip和端口映射(路由器转发)操作和原理
  • 梯度消失问题:深度学习中的「记忆衰退」困境与解决方案
  • React 学习笔记4 Diffing/脚手架
  • 2025了,你知道electron-vite吗?
  • 网络原理——HTTP/HTTPS
  • ImageMagick命令行图片工具:批量实现格式转换与压缩,支持水印添加及GIF动态图合成
  • 2条命令,5秒安装,1秒启动!Vite项目保姆级上手指南
  • 鸿蒙NEXT界面交互全解析:弹出框、菜单、气泡提示与模态页面的实战指南
  • 开源的聚合支付系统源码/易支付系统 /三方支付系统
  • Erlang 利用 recon 排查热点进程
  • 人工智能之数学基础:分布函数对随机变量的概率分布情况进行刻画
  • 微信小程序 navigateTo 栈超过多层后会失效
  • 在 Delphi 5 中获取 Word 文档页数的方法
  • 小程序蓝牙低功耗(BLE)外围设备开发指南
  • 365 天技术创作手记:从一行代码到四万同行者的相遇