cuDNN深度解析:实战演练

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 = 28
		28                      // 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.0f
		inDesc,                 // 输入张量的描述符,定义输入数据的格式和维度
		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。😠

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

使者大牙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值