Computing

jhDNN - 2 : cuDNN Convolution Forward 방법 본문

Deep Learning/jhDNN

jhDNN - 2 : cuDNN Convolution Forward 방법

jhson989 2022. 4. 19. 23:43

앞선 포스터에서 cuDNN에 대한 개략적인 설명 및 설치 방법에 대하여 소개하였다.

 

jhDNN - 1 : cuDNN 소개 및 설치 (Ubuntu 18.04)

cuDNN cuDNN은 NVIDIA CUDA® Deep Neural Network (cuDNN) library로, 딥러닝 네트워크에서 자주 사용되는 primitives(ex, CNN, RNN, pooling, softmax, etc.)를 NVIDIA GPU를 이용해 가속화하여 제공하는 librar..

computing-jhson.tistory.com

오늘 포스터에서는 간단한 cuDNN convolution forward 예제를 바탕으로 cuDNN 프로그래밍 모델에 대해서 정리하고자 한다. [1], [2], [3]을 참고하여 정리하였다.

 

 

 

cuDNN Programming Model

시작하기 앞서 cuDNN developer guide의 cuDNN programming model에 대해서 정리하고 시작한다.

cuDNN의 모든 API는 context-based API인데, 즉 모든 cuDNN API(function)는 library context를 입력으로 받는다. 따라서 cuDNN application은 시작 시 library context(=handle)를 초기화해줘야 하며, application 종료 시 context를 release해줘야 한다. 이러한 방식은 multithreading 시 쉬운 프로그래밍 인터페이스 제공 및 CUDA stream과의 interoperability(한 프로그램에서 CUDA와 cuDNN을 같이 사용해도 된다)를 위하여 사용된다고 한다. 예를 들어 여러 개의 GPU가 각각 하나의 host thread에서 맡아 실행되는 상황을 가정하자. 이 경우 이러한 context-based API 방식은 각 host thread 하나마다 unique한 cuDNN handle을 create하도록 한다. 이후 어떤 handle을 입력으로 받은 cuDNN call은 자동으로 그 handle이 정의된 host thread에서 실행되며, 따라서 쉽게 cuDNN API를 특정 GPU device에서 호출할 수 있다.

cuDNN API가 사용하는 데이터의 경우, 해당 함수에서 사용되는 모든 데이터는 GPU memory에 저장되어 있음을 가정한다. 즉 cuDNN 함수 호출 전 필요한 데이터를 모두 미리 GPU 메모리에 전송해 놓아야 한다. 따라서 cudaMalloc과 cudaMemcpy 등의 low-level CUDA operation을 사용하여 cuDNN이 사용할 데이터를 GPU에 할당하고 데이터를 전송해 놓아야 한다.

 

 

 

Basic cuDNN Code Structure

Programming model에서 정의되듯, 애플리케이션 시작 시 cudnnHandle_t (=context)를 정의하고, 끝날 시 할당 해제해줘야 한다. 따라서 기본적으로 밑과 같은 형태의 코드 구조를 가진다.

#include <cudnn.h>
int main(void) {
    cudnnHandle_t cudnn;
    cudnnCreate(&cudnn);
    
    /* Doing something */
    /* 딥러닝 루틴 실행 */
    
    cudnnDestroy(cudnn);
    return 0;
}

cudnnHandle이 생성되고 제거되기 까지의 영역(주석으로 달린 딥러닝 루틴 실행 영역)에서 이제 cuDNN convolution forward를 위한 코드가 적혀진다. cuDNN에서 DNN layer를 실행하기 위해서는 layer의 입출력 데이터와 layer의 특징을 명시하는 descriptor를 정의해주어야 한다. 앞으로 저 딥러닝 루틴 영역에서 input, output, kernel에 대한 dscriptor를 정의줄 것이다.

 

 

 

Describe Input & Output

cuDNN에서 DNN layer를 실행하기 위해서는 DNN Layer의 Input과 Output에 대하여 descriptor를 정의하여 포맷 및 사이즈에 대하여 명시해주어야 한다. Layer의 input & output tensor는 cudnnTensorDescriptor_t를 통해 명시해준다.

이 예제에서는 convolution layer의 input으로 4차원(N개의 RGB 이미지 -> N*C*H*W), output으로 4차원 데이터(N개의 3차원 feature-map -> N*C*H*W)를 사용할 것이다.

Input & output tensor descriptor는 cudnnSetTensor4dDescriptor() 함수를 통해 생성한다.

// cudnnStatus_t cudnnSetTensor4dDescriptor( 
// 	      cudnnTensorDescriptor_t tensorDesc, 
//        cudnnTensorFormat_t     format, 
//        cudnnDataType_t         dataType, 
//        int                     n, 
//        int                     c, 
//        int                     h, 
//        int                     w
// )

cudnnSetTensor4dDescriptor(
        descriptor_input,
        /*LAYOUT*/CUDNN_TENSOR_NCHW, 
        /*DATATYPE*/data_type, 
        /*N*/BATCH_NUM, /*C*/ INPUT_C, /*H*/ INPUT_H, /*W*/ INPUT_W
);

cudnnSetTensor4dDescriptor(
        descriptor_output,
        /*LAYOUT*/CUDNN_TENSOR_NCHW, 
        /*DATATYPE*/data_type, 
        /*N*/BATCH_NUM, /*C*/ OUTPUT_C, /*H*/ OUTPUT_H, /*W*/ OUTPUT_W
);

 

 

 

Describe Convolution Kernel : Filter, Algorithm, Convolution Layer, Workspace

cuDNN에서 convolution layer를 실행하기 위해서는, fileter(weight), convolution algorithm, convolution layer, workspace에 대하여 명시해주어야 한다. 

// Filter (weights)
cudnnFilterDescriptor_t descriptor_filter;
cudnnCreateFilterDescriptor(&descriptor_filter);
cudnnSetFilter4dDescriptor(
    descriptor_filter,
    /*DATATYPE*/data_type, /*LAYOUT*/CUDNN_TENSOR_NCHW, 
    /*OUT_CH*/OUTPUT_C, /*IN_CH*/ INPUT_C, 
    /*KERNEL_H*/FILTER_H, /*KERNEL_W*/FILTER_W
);

// Layer 
cudnnConvolutionDescriptor_t descriptor_conv2d;
cudnnCreateConvolutionDescriptor(&descriptor_conv2d);
cudnnSetConvolution2dDescriptor(
    descriptor_conv2d,
    /*PAD_H*/PAD_H, /*PAD_W*/PAD_W, /*STRIDE_VERTICAL*/STRIDE_H, 
    /*STRIDE_HORIZONTAL*/STRIDE_W, /*DILATION_H*/DILATION_H, /*DILATION_W*/DILATION_W, 
    /*MODE*/CUDNN_CROSS_CORRELATION, /*DATATYPE*/data_type
);

// Forward algorithm
int num_conv2d_algo_forward;
cudnnConvolutionFwdAlgoPerf_t perf_conv2d_algo_forward;
cudnnFindConvolutionForwardAlgorithm(
    cudnn, descriptor_input, descriptor_filter, descriptor_conv2d, descriptor_output, 
    /*요청하는 알고리즘 개수*/1, /*리턴된 알고리즘 개수*/&num_conv2d_algo_forward, 
    /*알고리즘 리스트*/&perf_conv2d_algo_forward
);

// Calculate work-space size for forward pass
size_t bytes_workspace_forward;
cudnnGetConvolutionForwardWorkspaceSize(
	cudnn, descriptor_input, descriptor_filter, descriptor_conv2d, descriptor_output, 
    perf_conv2d_algo_forward.algo, &bytes_workspace_forward);

// Allocate GPU memory for workspace
float* d_workspace_forward;
cudaMalloc (&d_workspace_forward, bytes_workspace_forward)

 

 

Launch Convolution Kernel

앞서 정의된 descriptor들을 이용해 실제 convolution forward를 실행할 차례이다. cudnnConvolutionForward 함수를 통해 실행하며, 실제 데이터(d_input, d_filter, d_output)뿐만 아니라 그 데이터를 명시하는 descriptors(descriptor_input, descriptor_output, descriptor_filter)를 입력으로 주어야 한다. alpha는 scaling factor이며, beta는 C의 초기값을 얼마나 반영할지를 나타낸다. 즉 C = alpha*(AⓍB) + beta*C (Ⓧ는 correlation)이다.

const float alpha=1, beta=0;
cudnnConvolutionForward(cudnn,
                        /*ALPHA*/&alpha,
                        /*INPUT*/descriptor_input, d_input,
                        /*KERNEL*/descriptor_filter, d_filter,
                        /*LAYER*/descriptor_conv2d, perf_conv2d_algo_forward.algo, 
                        /*WORKSPACE*/d_workspace_forward, bytes_workspace_forward,
                        /*BETA*/&beta,
                        /*OUTPUT*/descriptor_output, d_output,
);

 

 

 

 

Reference

[1] https://docs.nvidia.com/deeplearning/cudnn/developer-guide/index.html

[2] https://docs.nvidia.com/deeplearning/cudnn/api/index.html

[3] http://www.goldsborough.me/cuda/ml/cudnn/c++/2017/10/01/14-37-23-convolutions_with_cudnn/