使用CuDNN进行卷积运算

本文介绍如何使用NVIDIA CuDNN库实现卷积操作,包括环境配置、数据准备及卷积过程。通过实例演示了如何对一张图片进行拉普拉斯变换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CuDNN

NVIDIA ® cuDNN is a GPU-accelerated library of primitives for deep neural networks. It provides highly tuned implementations of routines arising frequently in DNN applications:

  • Convolution forward and backward, including cross-correlation
  • Pooling forward and backward
  • Softmax forward and backward
  • Neuron activations forward and backward:
    • Rectified linear (ReLU)
    • Sigmoid
    • Hyperbolic tangent (TANH)
  • Tensor transformation functions
  • LRN, LCN and batch normalization forward and backward

ENV

  • Ubuntu 16.04
  • CUDA 9.0
  • CuDNN 7.0
  • GCC 5.4.0
  • Alchemy / OpenCV
  • CLion 2018.1 RC2

cudnnConvolutionForward()

CuDNN中计算卷积操作的由cudnnConvolutionForward()来实现,其原型为

cudnnStatus_t CUDNNWINAPI cudnnConvolutionForward(
                                cudnnHandle_t                       handle,
                                const void                         *alpha,
                                const cudnnTensorDescriptor_t       xDesc,
                                const void                         *x,
                                const cudnnFilterDescriptor_t       wDesc,
                                const void                         *w,
                                const cudnnConvolutionDescriptor_t  convDesc,
                                cudnnConvolutionFwdAlgo_t           algo,
                                void                               *workSpace,
                                size_t                              workSpaceSizeInBytes,
                                const void                         *beta,
                                const cudnnTensorDescriptor_t       yDesc,
                                void                               *y );

其中:

  • x为输入数据的地址,w为卷积核的地址,y为输出数据的地址,对应的xDescwDescyDesc为描述这三个数据的描述子,比如记录了数据的batch size、channels、height和width等。
  • alpha对卷积结果x*w进行缩放,beta对输出y进行缩放,其表达式为:
dstValue = alpha[0]*computedValue + beta[0]*priorDstValue
  • workspace是指向进行卷积操作时需要的GPU空间的指针
  • workSpaceSizeInBytes为该空间的大小
  • algo用来指定使用什么算法来进行卷积运算
  • handle是创建的library context的句柄,使用CuDNN库必须用cudaCreate()来初始化。

实例

对图片进行拉普拉斯变换。

1. 准备图像

    auto image = imread("red.png");
    auto image_float = Matrix32f(image);

2. 准备使用CUDNN的环境

    //handle
    cudnnHandle_t handle;
    cudnnCreate(&handle);

3. 准备输入数据

    // input
    Tensor<float> input({ 1, image.channels(), image.rows, image.cols });
    Memory::copy(image_float.count() * sizeof(float), input.gptr(), image_float.ptr());

    cudnnTensorDescriptor_t input_descriptor;
    cudnnCreateTensorDescriptor(&input_descriptor);
    cudnnSetTensor4dDescriptor(input_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               input.shape(0), input.shape(1), input.shape(2), input.shape(3));

input_descriptor保存了输入的信息,其中

  • CUDNN_TENSOR_NHWC是数据数据的结构,这表示,input为一个四维的张量,四个维度分别为
    • N: Number这里作为输入的图片个数,我们例子中只有一张图片,为1
    • H: Height,即图片的高
    • W: Width,即图片的宽
    • C: Channels,即通道数,这里是图片的通道数,如果输入为RGB三通道则为3,灰度图则为1,依次类推
  • CUDNN_DATA_FLOAT为计算的数据类型

4. 准备输出数据

    // output
    Tensor<float> output(input.shape());
    vector_set_gpu(output.count(), 0.0f, output.gptr());

    cudnnTensorDescriptor_t output_descriptor;
    cudnnCreateTensorDescriptor(&output_descriptor);
    cudnnSetTensor4dDescriptor(output_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               output.shape(0), output.shape(1), output.shape(2), output.shape(3));

5. 准备卷积核

// kernel
    Tensor<float> kernel({ output.shape(1), input.shape(1), 3, 3 });
    auto kernel_size = kernel.count(2, 4);
    float kernel_[kernel_size] = { 0, 1, 0, 1, -4, 1, 0, 1, 0 };
    for(auto i = 0; i < kernel.count(0, 2); ++i) {
        memcpy(kernel.cptr() + i * kernel_size, kernel_, kernel_size * sizeof(float));
    }

    cudnnFilterDescriptor_t kernel_descriptor;
    cudnnCreateFilterDescriptor(&kernel_descriptor);
    cudnnSetFilter4dDescriptor(kernel_descriptor,
                               CUDNN_DATA_FLOAT,
                               CUDNN_TENSOR_NCHW,
                               kernel.shape(0), kernel.shape(1), kernel.shape(2), kernel.shape(3));

6. 卷积的描述子

    // convolution descriptor
    cudnnConvolutionDescriptor_t conv_descriptor;
    cudnnCreateConvolutionDescriptor(&conv_descriptor);
    cudnnSetConvolution2dDescriptor(conv_descriptor,
                                    1, 1, // zero-padding
                                    1, 1, // stride
                                    1, 1,
                                    CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);

7. 选择算法

    // algorithm
    cudnnConvolutionFwdAlgo_t algo;
    cudnnGetConvolutionForwardAlgorithm(handle,
                                        input_descriptor,
                                        kernel_descriptor,
                                        conv_descriptor,
                                        output_descriptor,
                                        CUDNN_CONVOLUTION_FWD_PREFER_FASTEST,
                                        0,
                                        &algo);

告诉CuDNN优先使用计算速度快的算法,并且没有内存限制

8. 准备计算所用的空间

    // workspace size && allocate memory
    size_t workspace_size = 0;
    cudnnGetConvolutionForwardWorkspaceSize(handle,
                                            input_descriptor,
                                            kernel_descriptor,
                                            conv_descriptor,
                                            output_descriptor,
                                            algo,
                                            &workspace_size);

    void * workspace = nullptr;
    cudaMalloc(&workspace, workspace_size);

9. Convolution

    // convolution
    auto alpha = 1.0f, beta = 0.0f;
    cudnnConvolutionForward(handle,
                            &alpha, input_descriptor, input.gptr(),
                            kernel_descriptor, kernel.gptr(),
                            conv_descriptor, algo,
                            workspace, workspace_size,
                            &beta, output_descriptor, output.gptr());

10. 销毁

    // destroy
    cudaFree(workspace);

    cudnnDestroyTensorDescriptor(input_descriptor);
    cudnnDestroyTensorDescriptor(output_descriptor);
    cudnnDestroyConvolutionDescriptor(conv_descriptor);
    cudnnDestroyFilterDescriptor(kernel_descriptor);

    cudnnDestroy(handle);

效果

这里写图片描述

源代码

#include <cudnn_v7.h>
#include "alchemy.h"

using namespace std;
using namespace alchemy;

int main()
{
    // image
    auto image = imread("red.png");
    auto image_float = Matrix32f(image);

    //handle
    cudnnHandle_t handle;
    cudnnCreate(&handle);

    // input
    Tensor<float> input({ 1, image.channels(), image.rows, image.cols });
    Memory::copy(image_float.count() * sizeof(float), input.gptr(), image_float.ptr());

    cudnnTensorDescriptor_t input_descriptor;
    cudnnCreateTensorDescriptor(&input_descriptor);
    cudnnSetTensor4dDescriptor(input_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               input.shape(0), input.shape(1), input.shape(2), input.shape(3));

    // output
    Tensor<float> output(input.shape());
    vector_set_gpu(output.count(), 0.0f, output.gptr());

    cudnnTensorDescriptor_t output_descriptor;
    cudnnCreateTensorDescriptor(&output_descriptor);
    cudnnSetTensor4dDescriptor(output_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               output.shape(0), output.shape(1), output.shape(2), output.shape(3));

    // kernel
    Tensor<float> kernel({ output.shape(1), input.shape(1), 3, 3 });
    auto kernel_size = kernel.count(2, 4);
    float kernel_[kernel_size] = { 0, 1, 0, 1, -4, 1, 0, 1, 0 };
    for(auto i = 0; i < kernel.count(0, 2); ++i) {
        memcpy(kernel.cptr() + i * kernel_size, kernel_, kernel_size * sizeof(float));
    }

    cudnnFilterDescriptor_t kernel_descriptor;
    cudnnCreateFilterDescriptor(&kernel_descriptor);
    cudnnSetFilter4dDescriptor(kernel_descriptor,
                               CUDNN_DATA_FLOAT,
                               CUDNN_TENSOR_NCHW,
                               kernel.shape(0), kernel.shape(1), kernel.shape(2), kernel.shape(3));
    // convolution descriptor
    cudnnConvolutionDescriptor_t conv_descriptor;
    cudnnCreateConvolutionDescriptor(&conv_descriptor);
    cudnnSetConvolution2dDescriptor(conv_descriptor,
                                    1, 1, // zero-padding
                                    1, 1, // stride
                                    1, 1,
                                    CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);

    // algorithm
    cudnnConvolutionFwdAlgo_t algo;
    cudnnGetConvolutionForwardAlgorithm(handle,
                                        input_descriptor,
                                        kernel_descriptor,
                                        conv_descriptor,
                                        output_descriptor,
                                        CUDNN_CONVOLUTION_FWD_PREFER_FASTEST,
                                        0,
                                        &algo);

    // workspace size && allocate memory
    size_t workspace_size = 0;
    cudnnGetConvolutionForwardWorkspaceSize(handle,
                                            input_descriptor,
                                            kernel_descriptor,
                                            conv_descriptor,
                                            output_descriptor,
                                            algo,
                                            &workspace_size);

    void * workspace = nullptr;
    cudaMalloc(&workspace, workspace_size);

    // convolution
    auto alpha = 1.0f, beta = 0.0f;
    cudnnConvolutionForward(handle,
                            &alpha, input_descriptor, input.gptr(),
                            kernel_descriptor, kernel.gptr(),
                            conv_descriptor, algo,
                            workspace, workspace_size,
                            &beta, output_descriptor, output.gptr());

    Matrix32f output_image(image.shape());
    cudaMemcpy(output_image.ptr(), output.gptr(), image.count() * sizeof(float), cudaMemcpyDeviceToHost);

    // destroy
    cudaFree(workspace);

    cudnnDestroyTensorDescriptor(input_descriptor);
    cudnnDestroyTensorDescriptor(output_descriptor);
    cudnnDestroyConvolutionDescriptor(conv_descriptor);
    cudnnDestroyFilterDescriptor(kernel_descriptor);

    cudnnDestroy(handle);


    // show
    imshow("original", image);
    imshow("output", Matrix(output_image/3.0));

    waitKey(0);
    return 0;
}
使用 cuDNN 训练深度学习模型可以显著提高性能,尤其是在卷积神经网络 (CNN) 中的表现。cuDNN 是 NVIDIA 提供的一个用于加速深度学习应用的库,它优化了许多常见的深层神经网络原语操作。下面是详细介绍如何配置和使用 cuDNN 来训练模型的过程。 ### 准备阶段 #### 1. 安装 NVIDIA 驱动程序 确保计算机已经安装了最新的 NVIDIA 显卡驱动,并且显卡支持 CUDA 技术。可以从 [NVIDIA 驱动下载页面](https://www.nvidia.cn/Download/index.aspx) 下载适合的操作系统的驱动包。 #### 2. 安装 CUDA Toolkit 前往 [NVIDIA CUDA 开发者网站](https://developer.nvidia.com/cuda-downloads),根据操作系统、架构等信息选择正确的 CUDA 版本进行安装。CUDA Toolkit 包含编译工具链和其他必要的组件来构建基于 GPU 的应用程序。 #### 3. 安装 cuDNN 库 访问 [cuDNN Archive 页面](https://developer.nvidia.com/rdp/cudnn-archive),登录后下载与当前使用CUDA 版本相匹配的 cuDNN 文件。解压缩文件并将其中包含的头文件 (`*.h`) 和动态链接库 (`lib64/*.so`, Windows 上则是 `bin/*dll` 和 `include/*.h`) 复制到相应的 CUDA 目录下: - Linux/macOS: `/usr/local/cuda/include` and `/usr/local/cuda/lib64` - Windows: `%CUDA_PATH%\include` and `%CUDA_PATH%\bin` 然后添加环境变量(如果尚未设置),让系统能找到新安装的 cuDNN 库路径。可以在终端或命令提示符中执行以下命令之一(取决于平台): - **Linux/macOS**: `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64` - **Windows**: 将 `%CUDA_PATH%\bin` 添加至 PATH 环境变量 ### 使用 cuDNN 训练模型 一旦完成了上述准备工作,接下来就可以开始编写代码利用 cuDNN 功能了。这里以 PyTorch 和 TensorFlow 为例说明具体步骤: #### 对于 PyTorch 用户 大多数情况下不需要特别修改代码即可享受 cuDNN 带来的提速效果,因为默认情况下 PyTorch 已经启用了对 cuDNN 的支持。不过还是建议检查是否确实加载了 cuDNN: ```python import torch print(torch.backends.cudnn.enabled) ``` 如果你想调整某些参数比如算法选择策略,则可以通过如下方式设置: ```python torch.backends.cudnn.benchmark = True # 启用非确定性的高效模式 # 或者禁用 cuDNN 卷积自动调优机制以减少内存占用但可能会慢些 torch.backends.cudnn.deterministic = False ``` 此外,在实例化模型之后记得将其移动到 GPU 设备上去: ```python device = 'cuda' if torch.cuda.is_available() else 'cpu' model.to(device=device) ``` #### 对于 TensorFlow 用户 类似地,默认启用 cuDNN 能力,只需要确认正确设置了虚拟环境中所依赖的所有软件包版本之间的兼容性。有时可能需要明确指定要使用哪一种数据格式作为输入张量布局风格: ```python from tensorflow.keras import backend as K K.set_image_data_format('channels_first') # NCHW 格式更适合 GPU 运算 ``` 同时也要确保模型放置到了适当的设备上: ```python with tf.device('/GPU:0'): model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size) ``` 另外需要注意的是,如果你遇到由于 cuDNN 内核找不到导致的问题,可能是由于硬件资源不足或者是超出了允许的最大尺寸限制引起的;此时要么尝试减小批量大小或是更改图像分辨率,要么考虑升级硬件设施。 最后别忘了定期保存好自己的进度以免意外丢失!
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值