NIVIDIA高性能计算CUDA笔记(四)cuDNN深度神经网络计算库简介及卷积操作示例

NIVIDIA高性能计算CUDA笔记(四)

cuDNN深度神经网络计算库简介及卷积操作示例

\(cuDNN\),全称为NIVIDIA CUDA Deep Neural Network Library,是深度神经网络算子层级GPU加速库集合,提供了深度学习算法中常见算子的高效实现,专门为深度学习框架(如\(TensorFlow\),\(PyTorch\),\(Caffe\), \(MXNet\)等)实现常见的神经网络层提供极致的优化的实现 ,所以也直接成为了很多上层推理引擎底层调优的算子备选实现,比如\(TensorRT\),比如\(TVM\)。再换个角度理解,之前在该系列笔记的第一篇提到宗旨“解读介绍英伟达软件生态之CUDA”中介绍了GPU的编程语言\(\text{CUDA}\) C“,调用\(\text{CUDA}\) C其实就是为了写一些kernel,而这里的\(cuDNN\) 则是提供一些写好的高效的CUDA C kernel的集合。正如,该系列笔记介绍的\(cuFFT\)\(cuBLAS\) ,区别就在于它们面向的应用场景不同,这里的\(cuDNN\)就是面向深度神经网络算子的高效实现。

1.cuDNN的句柄与描述子

\(\text{cuDNN}\)里,几乎所有操作都需要以下几个基本对象,我们这些基本对象进行描述:

A.句柄(cudnnHandle_t)

​ 在使用cuDNN进行任何操作之前,需要初始化cuDNN上下文。可以使用cudnnCreate()函数来创建一个cuDNN上下文句柄,后续的所有cuDNN操作都将基于这个句柄进行。句柄(cudnnHandle_t)是cuDNN中一个非常重要的概念,它本质是一个指向\(cuDNN\)内部状态数据数据结构的指针,句柄的作用类似于“会化ID”,cuDNN通过它识别并管理当前的计算环境,确保所有操作在正确的上下文(如特定GPU设备、资源分配状态等)中执行。

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

cudnnStatus_t cudnnCreate(cudnnHandle_t *handle);
cudnnStatus_t cudnnDestroy(cudnnHandle_t handle);
  • 作用:创建\(\text{cuDNN}\)上下文句柄,类似于\(\text{CUDA runtime}\)\(cudaStream\)。所有操作都依赖它;
  • 参数\(handle\)\(\text{cuDNN}\)运行环境的句柄;
  • 创建与销毁:句柄通过cudnnCreate()函数创建,在使用完成后,必须通过cudnnDestroy()函数进行销毁,以释放句柄所占用的资源,避免内存泄漏;
  • 线性安全性:cuDNN句柄不是线性安全的,每个线程应该使用独立的句柄对象。如果在多个线程环境中使用同一个句柄,可能导致不可预测的错误。因此,在多线程编程时,需要为每个线程单独创建和管理句柄。
  • 与CUDA上下文的关联:cuDNN句柄通常是管理GPU资源的基础,cuDNN句柄通过关联的CUDA上下文来访问GPU硬件资源,进行数据传输和计算操作。

cudnnStatus_t是cuDNN所有函数调用的返回状态类型,理解它非常重要,因为它决定了你能否成功调用\(\text{cuDNN API}\)


typedef enum {
    CUDNN_STATUS_SUCCESS = 0,
    CUDNN_STATUS_NOT_INITIALIZED = 1,
    CUDNN_STATUS_ALLOC_FAILED = 2,
    CUDNN_STATUS_BAD_PARAM = 3,
    CUDNN_STATUS_INTERNAL_ERROR = 4,
    CUDNN_STATUS_INVALID_VALUE = 5,
    CUDNN_STATUS_ARCH_MISMATCH = 6,
    CUDNN_STATUS_MAPPING_ERROR = 7,
    CUDNN_STATUS_EXECUTION_FAILED = 8,
    CUDNN_STATUS_NOT_SUPPORTED = 9,
    CUDNN_STATUS_LICENSE_ERROR = 10,
    CUDNN_STATUS_RUNTIME_PREREQUISITE_MISSING = 11
} cudnnStatus_t;
枚举值 含义 说明
CUDNN_STATUS_SUCCESS 成功 调用成功,没有错误
CUDNN_STATUS_NOT_INITIALIZED 未初始化 cuDNN handle 未创建或初始化失败
CUDNN_STATUS_ALLOC_FAILED 内存分配失败 GPU 内存不足,cudaMalloc 失败
CUDNN_STATUS_BAD_PARAM 参数错误 函数参数有误,类型或维度不匹配
CUDNN_STATUS_INTERNAL_ERROR 内部错误 cuDNN 内部执行错误,不是你传的参数问题
CUDNN_STATUS_INVALID_VALUE 数值错误 数值超出范围,例如 stride、padding 不合理
CUDNN_STATUS_ARCH_MISMATCH 架构不匹配 当前 GPU 架构不支持所选算法
CUDNN_STATUS_MAPPING_ERROR 内存映射错误 GPU 内存映射出错
CUDNN_STATUS_EXECUTION_FAILED 执行失败 kernel 执行失败,例如溢出或 cuda kernel 错误
CUDNN_STATUS_NOT_SUPPORTED 不支持 该功能或数据类型不被支持
CUDNN_STATUS_LICENSE_ERROR 许可错误 需要授权许可,但未满足条件
CUDNN_STATUS_RUNTIME_PREREQUISITE_MISSING 依赖缺失 运行时依赖库缺失或版本不匹配

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

B.张量描述子(cudnnTensorDescriptor_t)

​ 张量描述符(cudnnTensorDescriptor_t) 用于描述各种对象属性的数据结构,它为cuDNN函数提供了执行操作所需的元数据信息。包括数据张量的(如批量大小、通道数、高度、宽度等)、数据类型(如CUDNN_DATA_FLOAT表示32位浮点数)、张量布局(如CUDNN_TENSOR_NCHW表示数据批量-通道-高度-宽度的布局)。通过cudnnCreateTensorDescriptor() 创建,cudnnSetTensor4dDescriptor()等函数设置属性,cudnnDestroyDescriptor()销毁。

cudnnStatus_t cudnnCreateTensorDescriptor(cudnnTensorDescriptor_t *tensorDesc);
cudnnStatus_t cudnnSetTensor4dDescriptor(
    cudnnTensorDescriptor_t tensorDesc,
    cudnnTensorFormat_t format,   // NCHW 或 NHWC
    cudnnDataType_t dataType,     // float, half, double...
    int n, int c, int h, int w    // 维度
);
cudnnStatus_t cudnnDestroyTensorDescriptor(cudnnTensorDescriptor_t tensorDesc);

作用:描述输入、输出张量的维度和数据类型。

张量在\(cuDNN\)中通常按照\(\text{4D格式(N,C,H,W)}\)(假设NCHW格式)存储:

  • N:batch size,代表一批量输入样本的数量;
  • C:通道数(例如\(RGB\)图像就是3个通道;灰度图就是1个通道);
  • H:图像的行数,高度
  • W: 图像的列数,宽度

cudnnTensorFormat_t代表张量的格式

typedef enum {
    CUDNN_TENSOR_NCHW = 0,      // [batch, channels, height, width]
    CUDNN_TENSOR_NHWC = 1,      // [batch, height, width, channels]
    CUDNN_TENSOR_NCHW_VECT_C = 2  // 向量化通道格式 (主要用于部分卷积加速)
} cudnnTensorFormat_t;
枚举值 说明
CUDNN_TENSOR_NCHW 最常用格式,张量按 [N, C, H, W] 存储,适合大多数卷积、批量计算。
CUDNN_TENSOR_NHWC 张量按 [N, H, W, C] 存储,在某些 GPU 架构(如 Tensor Core)上可能更快
CUDNN_TENSOR_NCHW_VECT_C 向量化通道存储格式,一般用于卷积加速,通道数必须是 4 的倍数

cudnnDataType_t\(\text{cuDNN}\)库中用于指定张量数据类型的枚举类型,它定义了深度学习操作中支持的数据格式,包括精度和存储方式。

typedef enum {
    CUDNN_DATA_FLOAT = 0,       // 32-bit float
    CUDNN_DATA_DOUBLE = 1,      // 64-bit double
    CUDNN_DATA_HALF = 2,        // 16-bit float (FP16)
    CUDNN_DATA_INT8 = 3,        // 8-bit integer
    CUDNN_DATA_INT32 = 4,       // 32-bit integer
    CUDNN_DATA_INT8x4 = 5,      // 8-bit integer pack of 4
    CUDNN_DATA_UINT8 = 6,       // 8-bit unsigned integer
    CUDNN_DATA_INT8x32 = 7      // 8-bit integer pack of 32
} cudnnDataType_t;

其枚举值的含义如下:

cudnnDataType_t枚举值 数据类型 参数说明
CUDNN_DATA_FLOAT float32 最常用数据类型,精度高
CUDNN_DATA_DOUBLE float64 双精度,GPU 上使用少,性能低
CUDNN_DATA_HALF float16 半精度,适合 Tensor Core 加速
CUDNN_DATA_INT8 int8 量化神经网络使用
CUDNN_DATA_INT32 int32 常用于累加或输出量化后的结果
CUDNN_DATA_INT8x4 / INT8x32 int8 pack 8-bit 数据打包存储,加速卷积
CUDNN_DATA_UINT8 uint8 无符号 8-bit 数据

C.卷积描述子(cudnnConvolutionDescriptor_t)

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


cudnnStatus_t cudnnCreateConvolutionDescriptor(cudnnConvolutionDescriptor_t *convDesc);
cudnnStatus_t cudnnSetConvolution2dDescriptor(
    cudnnConvolutionDescriptor_t convDesc,
    int pad_h, int pad_w,         // 填充
    int u, int v,                 // 步长 (stride)
    int dilation_h, int dilation_w,
    cudnnConvolutionMode_t mode,  // CUDNN_CONVOLUTION or CUDNN_CROSS_CORRELATION
    cudnnDataType_t computeType   // 计算精度
);
cudnnStatus_t cudnnDestroyConvolutionDescriptor(cudnnConvolutionDescriptor_t convDesc);

cudnnConvolutionMode_t 是cuDNN库中用于指定卷积操作类型的枚举类型,主要定义两种模式:标准卷积互相关

typedef enum {
    CUDNN_CONVOLUTION = 0,           // 标准卷积
    CUDNN_CROSS_CORRELATION = 1      // 互相关 (默认)
} cudnnConvolutionMode_t;

其枚举值含义:

枚举值 含义 说明
CUDNN_CONVOLUTION 卷积 卷积核会翻转 180° 再做滑动窗口求和。数学上是标准卷积公式。
CUDNN_CROSS_CORRELATION 互相关 卷积核不翻转,直接在输入上做滑动窗口求和。cuDNN 默认使用这个模式。

注意:大多数深度学习框架(PyTorch、TensorFlow)默认使用互相关,所以\(\text{cuDNN}\)也默认使用CUDNN_CROSS_CORRELATION

过滤器(卷积核)描述子(cudnnFilterDescriptor_t) 是cuDNN中用于描述卷积滤波器(权重)张量的核心数据类型。它是cuDNN卷积操作中不同或缺的组成部分。由cudnnCreateConvolutionDescriptor()来创建,cudnnSetFilter4dDescriptor()来设置卷积算子,cudnnDestroyFilterDescriptor() 销毁;

cudnnStatus_t cudnnCreateFilterDescriptor(cudnnFilterDescriptor_t *filterDesc);
cudnnStatus_t cudnnSetFilter4dDescriptor(
    cudnnFilterDescriptor_t filterDesc,
    cudnnDataType_t dataType, cudnnTensorFormat_t format,
    int k, int c, int h, int w
);
cudnnStatus_t cudnnDestroyFilterDescriptor(cudnnFilterDescriptor_t filterDesc);

D. 卷积操作算子(cudnnConvolutionForward)

计算卷积算子的输出尺寸;

cudnnStatus_t cudnnGetConvolution2dForwardOutputDim(
    const cudnnConvolutionDescriptor_t convDesc, // 卷积描述子
    const cudnnTensorDescriptor_t inputTensorDesc, // 输入张量描述子
    const cudnnFilterDescriptor_t filterDesc,      // 卷积核描述子
    int* n, int* c, int* h, int* w                 // 输出张量尺寸
);
参数 类型 说明
convDesc cudnnConvolutionDescriptor_t 卷积参数描述子,包括 padding、stride、dilation、卷积模式等
inputTensorDesc cudnnTensorDescriptor_t 输入张量描述子,包含 N、C、H、W 以及数据类型、存储格式
filterDesc cudnnFilterDescriptor_t 卷积核描述子,包含 K、C、R、S
n, c, h, w int* 输出张量的 N、C、H、W,由函数计算后返回

对于输入尺寸\(H_{in}\times W_{in}\) 和卷积核\(R\times{S}\):

\[\begin{aligned} &H_{out}=\left[\frac{H_{in}+2\cdot pad_h-dilation_h\cdot(R-1)-1}{2}+1\right]\\ &W_{out}=\left[\frac{W_{out}+2\cdot pad_h-dilation_w\cdot(S-1)-1}{2}+1\right] \end{aligned} \]

选择卷积算法:

调用 cuDNN 核心 API,在 GPU 上执行 3D 卷积计算,结果存储到 GPU 输出内存 d_output 中。

float alpha = 1.0f, beta = 0.0f;
CHECK_CUDNN(cudnnConvolutionForward(cudnn, &alpha, inputDesc, d_input, filterDesc, d_filter,convDesc, perfResults.algo, d_workspace, workspace_bytes, &beta, outputDesc, d_output));

alpha=1.0f、beta=0.0f:卷积计算的系数,对应公式:output=α×conv(input,kernel)+β×output

cudnnConvolutionForward:cuDNN 前向卷积核心 API,参数依次是:
cuDNN 句柄 → 系数 α → 输入描述符 → GPU 输入数据 → 卷积核描述符 → GPU 卷积核 → 卷积规则描述符 → 最优算法 → 工作空间 → 工作空间大小 → 系数 β → 输出描述符 → GPU 输出数据。

2.卷积案例

在 CUDA 上通过 cuDNN 进行 3D 卷积计算一般需要做以下几步工作:

  1. 初始化 cuDNN 句柄;创建 cuDNN 的核心句柄,作为后续所有 cuDNN 操作的基础(cudnnCreate()接口)。
  2. 构造输入张量描述符;定义输入张量的维度(NCHW 格式,包含批次、通道、深度 / 高度 / 宽度)、数据类型和内存步长,步长通过自定义compute_stride函数计算。涉及cudnnCreateTensorDescriptor()cudnnSetTensorNdDescriptor()接口。
  3. 构造卷积核(滤波器)描述符;定义卷积核的维度(输出通道、输入通道、核深度 / 高度 / 宽度)、数据类型和存储格式。涉及cudnnCreateFilterDescriptor()cudnnSetFilterNdDescriptor()接口。
  4. 计算 SAME 填充并构造卷积描述符;
    • 先计算 SAME 卷积模式下的填充值,再设置卷积的步长、膨胀系数,指定卷积计算模式(如互相关)。
    • 涉及cudnnCreateConvolutionDescriptor()cudnnSetConvolutionNdDescriptor()接口。
  5. 计算输出张量尺寸并构造输出张量描述符;通过 cuDNN 接口自动计算卷积输出的张量尺寸,再定义输出张量的维度、数据类型和内存步长。涉及cudnnGetConvolutionNdForwardOutputDim()cudnnCreateTensorDescriptor()cudnnSetTensorNdDescriptor()接口。
  6. 选取最优卷积算法并计算工作空间大小;
    • 获取 cuDNN 推荐的最优前向卷积算法,同时计算该算法所需的 GPU 工作空间大小。
    • 涉及cudnnGetConvolutionForwardAlgorithm_v7()cudnnGetConvolutionForwardWorkspaceSize()接口。
  7. 分配 GPU 设备内存并拷贝数据;在设备端为输入、卷积核、输出张量及工作空间分配内存,将主机端的输入数据和卷积核数据拷贝到设备端(cudaMalloccudaMemcpy(cudaMemcpyHostToDevice)接口)。
  8. 执行 3D 卷积前向计算;调用 cuDNN 卷积前向计算接口,使用选定的最优算法完成 3D 卷积核心计算。涉及cudnnConvolutionForward()接口。
  9. 拷贝输出数据到主机端;将设备端完成卷积计算后的输出数据拷贝回主机端(cudaMemcpy(cudaMemcpyDeviceToHost)接口)。
  10. 资源释放;若不再需要相关资源,释放设备端分配的内存,销毁 cuDNN 的各类描述符(张量、卷积核、卷积)和 cuDNN 句柄。涉及cudaFree()cudnnDestroyTensorDescriptor()cudnnDestroyFilterDescriptor()cudnnDestroyConvolutionDescriptor()cudnnDestroy()接口。
void compute_stride(const int* size, int* stride) {
    for (int i = 4; i >= 0; i--)
        stride[i] = (i == 4) ? 1 : size[i + 1] * stride[i + 1];
}

void cudnn_conv_3d(const float* input, const float* kernel,
    float* output, const int input_dims[3],
    const int kernel_dims[3]) {
    // -------------------------- 1. 初始化CUDNN句柄 --------------------------
    cudnnHandle_t cudnn;
    CHECK_CUDNN(cudnnCreate(&cudnn));

    // -------------------------- 2. 构造输入张量描述符 --------------------------
    int inputDims[5] = { 1, 1, input_dims[0], input_dims[1], input_dims[2] }; // N=1, C=1, D/H/W
    int input_stride[5];
    compute_stride(inputDims, input_stride);

    cudnnTensorDescriptor_t inputDesc;
    CHECK_CUDNN(cudnnCreateTensorDescriptor(&inputDesc));
    CHECK_CUDNN(cudnnSetTensorNdDescriptor(inputDesc, CUDNN_DATA_FLOAT, 5, inputDims, input_stride));


    // -------------------------- 3. 构造卷积核描述符 --------------------------
    int filterDims[5] = { 1, 1, kernel_dims[0], kernel_dims[1], kernel_dims[2] }; // 输出通道=1, 输入通道=1, 核尺寸
    cudnnFilterDescriptor_t filterDesc;
    CHECK_CUDNN(cudnnCreateFilterDescriptor(&filterDesc));
    CHECK_CUDNN(cudnnSetFilterNdDescriptor(filterDesc, CUDNN_DATA_FLOAT, CUDNN_TENSOR_NCHW, 5, filterDims));

    // -------------------------- 4. 计算SAME填充并构造卷积描述符 --------------------------
    int conmv_padA[3];
    for (int i = 0; i < 3; i++) {
        conmv_padA[i] = (kernel_dims[i] - 1) / 2; // 计算SAME填充
    }
    int conv_filterStrideA[3] = { 1, 1, 1 };       // 步长=1
    int conv_dilationA[3] = { 1, 1, 1 };           // 膨胀=1

    cudnnConvolutionDescriptor_t convDesc;
    CHECK_CUDNN(cudnnCreateConvolutionDescriptor(&convDesc));
    CHECK_CUDNN(cudnnSetConvolutionNdDescriptor(convDesc, 3, conmv_padA, conv_filterStrideA,
        conv_dilationA, CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT));


    // -------------------------- 5. 计算输出张量尺寸并构造描述符 --------------------------
    int outputDims[5];
    CHECK_CUDNN(cudnnGetConvolutionNdForwardOutputDim(convDesc, inputDesc, filterDesc, 5, outputDims));
    int output_stride[5];
    compute_stride(outputDims, output_stride);

    cudnnTensorDescriptor_t outputDesc;
    CHECK_CUDNN(cudnnCreateTensorDescriptor(&outputDesc));
    CHECK_CUDNN(cudnnSetTensorNdDescriptor(outputDesc, CUDNN_DATA_FLOAT, 5, outputDims, output_stride));

    // -------------------------- 6. 选取最优卷积算法 --------------------------
    size_t in_bytes, out_bytes;
    CHECK_CUDNN(cudnnGetTensorSizeInBytes(inputDesc, &in_bytes));
    CHECK_CUDNN(cudnnGetTensorSizeInBytes(outputDesc, &out_bytes));

    size_t filt_bytes = 1;
    for (int i = 0; i < 5; i++) filt_bytes *= filterDims[i];
    filt_bytes *= sizeof(float);

    // 获取最优卷积算法
    int returnedAlgoCount = 0;
    cudnnConvolutionFwdAlgoPerf_t perfResults;
    CHECK_CUDNN(cudnnGetConvolutionForwardAlgorithm_v7(cudnn, inputDesc, filterDesc, convDesc, outputDesc,
        1, &returnedAlgoCount, &perfResults));

    // 计算工作空间大小
    size_t workspace_bytes = 0;
    CHECK_CUDNN(cudnnGetConvolutionForwardWorkspaceSize(cudnn, inputDesc, filterDesc, convDesc, outputDesc,
        perfResults.algo, &workspace_bytes));


    // -------------------------- 7. 分配设备内存并拷贝数据 --------------------------
    float* d_input = nullptr, * d_filter = nullptr, * d_output = nullptr;
    void* d_workspace = nullptr;

    CHECK_CUDA(cudaMalloc((void**)&d_input, in_bytes));
    CHECK_CUDA(cudaMalloc((void**)&d_filter, filt_bytes));
    CHECK_CUDA(cudaMalloc((void**)&d_output, out_bytes));


    if (workspace_bytes > 0) {
        CHECK_CUDA(cudaMalloc((void**)&d_workspace, workspace_bytes));
    }

    // 主机到设备拷贝
    CHECK_CUDA(cudaMemcpy(d_input, input, in_bytes, cudaMemcpyHostToDevice));
    CHECK_CUDA(cudaMemcpy(d_filter, kernel, filt_bytes, cudaMemcpyHostToDevice));

    // -------------------------- 8. 执行卷积计算 --------------------------
    float alpha = 1.0f, beta = 0.0f;
    CHECK_CUDNN(cudnnConvolutionForward(cudnn, &alpha, inputDesc, d_input, filterDesc, d_filter,
        convDesc, perfResults.algo, d_workspace, workspace_bytes,
        &beta, outputDesc, d_output));

    // -------------------------- 9. 设备到主机拷贝输出 --------------------------
    CHECK_CUDA(cudaMemcpy(output, d_output, out_bytes, cudaMemcpyDeviceToHost));

    // -------------------------- 10. 资源释放 --------------------------
    CHECK_CUDA(cudaFree(d_input));
    CHECK_CUDA(cudaFree(d_filter));
    CHECK_CUDA(cudaFree(d_output));
    CHECK_CUDA(cudaFree(d_workspace));

    CHECK_CUDNN(cudnnDestroyTensorDescriptor(outputDesc));
    CHECK_CUDNN(cudnnDestroyConvolutionDescriptor(convDesc));
    CHECK_CUDNN(cudnnDestroyFilterDescriptor(filterDesc));
    CHECK_CUDNN(cudnnDestroyTensorDescriptor(inputDesc));
    CHECK_CUDNN(cudnnDestroy(cudnn));
}
#define _CRT_SECURE_NO_WARNINGS

#include "rscudaalgo.h"
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <cmath>
#include <vector>
#include "fundamental.h"
#include "segy.h"
#include "alloc.h"
#include "qdebug.h"

//测试三维卷积
void test_conv3d() {

	const char* filename_input = "smallCube.sgy";   //输入文件名
	const char* filename_output = "conv_small.sgy";            //输出文件名

	FILE* fp_input = nullptr;                                 //输入文件的文件指针
	FILE* fp_output = nullptr;                                 //输出文件的文件指针

	bhed fileheader;                                           //定义文件头
	segy* traceheader_array = nullptr;                                 //道头数组指针

	float* datacube_input = nullptr;                        //输入地震数据的三维指针
	float* datacube_output = nullptr;                       //输出地震数据的三维指针

	unsigned int size_fileheader = sizeof(fileheader);
	unsigned int size_traceheader = sizeof(segy);

	unsigned int nline = 0;                                        //三维地震数据的测线数
	unsigned int ncdp = 0;                                        //三维地震数据的CDP数
	unsigned int nt = 0;                                         //三维地震数据的时间采样数
	long long ntrace = 0;
	long long size_inputcube = 0;
	long long size_trace = 0;
	
	nline = 100;
	ncdp = 500;

	fp_input = fopen(filename_input, "rb");
	if (fp_input == nullptr) {
		printf("Cannot open this input file!!!\n");
		qDebug() << "Cannot open this input file!!!\n";
		system("pause");
		return;
	}//end if(fp_input==nullptr)

	fp_output = fopen(filename_output, "wb");
	if (fp_output == nullptr) {
		printf("Cannot open\create this output file!!!\n");
        qDebug() << "Cannot open\create this output file!!!\n";
		system("pause");
		return;
	}//end if(fp_output==nullptr)

	/*判断地震数据是否为规则的*/
	fread(&fileheader, size_fileheader, 1, fp_input);
	nt = fileheader.hns;

	size_trace = size_traceheader + nt * sizeof(float);
	_fseeki64(fp_input, 0, SEEK_END);
	size_inputcube = _ftelli64(fp_input);
	ntrace = (size_inputcube - size_fileheader) / (size_trace);
	_fseeki64(fp_input, size_fileheader, SEEK_SET);

	if (ntrace != (nline * ncdp)) {
		printf("Cube is not regularized!!!\n");
		return;
	}

	/*读取规则的地震数据体及处理,输出*/
	int input_dims[3] = { nline, ncdp, nt };
	int nAllSize = input_dims[0] * input_dims[1] * input_dims[2];
	datacube_input = new float[nAllSize];
	memset(datacube_input, 0, nAllSize * sizeof(float));
	traceheader_array = (segy*)calloc(nline * ncdp, size_traceheader);
	datacube_output = new float[nAllSize];
	memset(datacube_output, 0, nline * ncdp * nt * sizeof(float));

	for (int iline = 0; iline < nline; iline++) {   //读取三维地震属性体
		for (int icdp = 0; icdp < ncdp; icdp++) {
			int nIndex = (iline * ncdp + icdp) * nt;
			//printf("iline=%d,icdp=%d,nIndex=%d\n", iline, icdp, nIndex);
			fread(&traceheader_array[iline * ncdp + icdp], size_traceheader, 1, fp_input);
			fread(&datacube_input[nIndex], nt * sizeof(float), 1, fp_input);
		}//end for(int icdp = 0; icdp < ncdp;icdp++)
	}//end for(int iline = 0; iline < nline; iline++)

	int cube2_dims[3] = { 21,21,21 };
	int nCube2Size = cube2_dims[0] * cube2_dims[1] * cube2_dims[2];
	float* pCube2 = new float[nCube2Size];
	memset(pCube2, 0, sizeof(float) * nCube2Size);
	getCube2(cube2_dims[0], cube2_dims[1], cube2_dims[2], pCube2);

	//开始计时
	clock_t start_clock, end_clock;
	start_clock = clock();

	// 初始化输入数据(主机到设备)
	// -------------------------- 8. 执行卷积并计时 --------------------------
	cudnn_conv_3d(datacube_input, pCube2, datacube_output, input_dims, cube2_dims);

	//结束计时
	end_clock = clock();
	double endtime = (double)(end_clock - start_clock) / CLOCKS_PER_SEC;
	std::cout << "Total time:" << endtime << " s" << std::endl;	//ms为单位
	qDebug() << "Total time:" << endtime << " s" << endl;

	fwrite(&fileheader, size_fileheader, 1, fp_output);
	for (int iline = 0; iline < nline; iline++) {   //写出三维地震属性体
		for (int icdp = 0; icdp < ncdp; icdp++) {
			int nIndex = (iline * ncdp + icdp) * nt;
			fwrite(&traceheader_array[iline * ncdp + icdp], size_traceheader, 1, fp_output);
			fwrite(&datacube_output[nIndex], nt * sizeof(float), 1, fp_output);
		}//end for(int icdp = 0; icdp < ncdp;icdp++)
	}//end for(int iline = 0; iline < nline; iline++)

	// -------------------------- 9. 释放资源 --------------------------
	fclose(fp_input);
	fclose(fp_output);

	delete[]datacube_input;
	delete[]datacube_output;

	free(traceheader_array);
}

int main() {

	test_conv3d();

    return 0;
}

posted @ 2026-01-08 18:19  GeoFXR  阅读(63)  评论(0)    收藏  举报