VS2019 CMake が Win&Linux デュアルプラットフォーム CUDA+cuDNN を開発

序文


cuDNN は TensorRT よりもはるかに遅いため、Nvidia のサーバー GPU に AI モデルをデプロイするには TensorRT を使用する必要があります。NvidiaGPUに基づく AI モデル構造の最適化に関する私の最新の記事を参照してください。

最後に、私の知る限り、TensorCore のパフォーマンスを完璧に実行できる唯一のエンジンは TensorRT です。一部のトレーニングおよび推論プラットフォーム (pytorch など) は実際に cuDNN を呼び出してモデルの推論を完了します。しかし、私の経験によると、同じ パラメータ畳み込み cuDNN の実装は TensorRT の実装より 3 倍以上遅く、TensorCore に基づいて畳み込みを実装しようとしましたが、最終的には TensorRT よりも 20% 以上遅くなります
。 ————— ———————
著作権声明: この記事は CSDN ブロガー「Mr_L_Y」のオリジナル記事であり、CC 4.0 BY-SA 著作権契約に従って、転載する場合は元のソースリンクとこの声明を添付してください。
元のリンク: https://blog.csdn.net/luoyu510183/article/details/117385131

 


最新のプロジェクトは、推論に Nvidia GPU を使用し、主に CUDA、cuDNN、TensorRT を使用して、サーバーによる AI アルゴリズムの実装を支援することです。また、cuDNN は現在ダイレクトをサポートしていないため、cuDNN を置き換える独立した CUDA エンジンも開発しています。この種の直接畳み込みアルゴリズムでは、GEMM、FFT、WINOGRAD の 3 つのアルゴリズムのみを使用できます。現在のテストでは、これら 3 つの方法が直接畳み込みよりも必ずしも高速であるとは限らないと感じています。また、cuDNN の TRUE_HALF_CONFIG は float Fast よりも高速に計算されません。直接畳み込みによる私の結論とは逆です。

まず構造的な関係を説明します。

CUDA は Nvidia GPU 開発用の基本ツール セットで、.cu コンパイラ nvcc だけでなく、npp、nvjpeg、nvblas などの多くの便利なツール ライブラリも含まれています。

cuDNN は CUDA ベースの畳み込みアルゴリズム ライブラリであり、Tensor の + - * /、活性化関数、畳み込み関数など、ニューラル ネットワークの畳み込み計算用の演算子が多数含まれています。

TensorRT は、cuDNN に基づくモデル指向のアプリケーション ライブラリです。ここで特定の畳み込みの実装を理解する必要はありません。必要なのは、モデルを TensorRT に渡し、対応するパーサーを使用して分析し、推論やトレーニングを実行することだけです。

または、構造は次のとおりです。

モデル => TensorRT パーサー => TensorRT cuDNN ラッパー => cuDNN ops => CUDA カーネル => GPU ドライバー

この記事は主に、基本的なデュアル プラットフォーム CUDA+cuDNN プロジェクトのコンパイルとビルドの入門として使用されます。 

文章

準備

CUDAをインストールする

最初に使用する必要があるツール セットは、主に VS2019 とその CMake プロジェクト テンプレートです。この記事は高度な内容であり、CMake の基本については説明しません。前の記事にアクセスして読むことができます。

VS のバージョンに特別な要件はありません。私は CUDA バージョン 10.1 を使用しており、ドライバーのバージョンは r418 以降です。CUDA10.2 も可能ですが、11.0 以降はテストしていません。 

公式 Web サイトhttps://developer.nvidia.com/Cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocalから CUDA をダウンロードします。

これが最新バージョンであることに注意してください。通常は最新バージョンを使用したいのですが、サーバー展開の要件により、バージョン 10.1 しかダウンロードできません。過去のバージョンをダウンロードするには、以下のリンクをクリックする必要があります。

URL: https://developer.nvidia.com/cuda-toolkit-archive

Windows のインストールについては何も言うことはありません。必要なコンポーネントを選択できるだけです。GPU ドライバーも同時にインストールされることに注意してください。一般に、10.1 ドライバーは比較的古いため、インストールしないことを選択できます。ドライバーをインストールし、cuda ツールキットのみをインストールします。

Linux のインストールについてはhttps://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.htmlを参照してください。必ず公式 Web サイトの手順に従ってインストールしてください。deb ローカル インストールを使用することをお勧めします。 。

cuDNNをインストールする

アドレス: https://developer.nvidia.com/rdp/cudnn-download

ここではまだ古いバージョン 7.6.5 をダウンロードしています

Windows のインストールを忘れていました。cuDNN はフォルダーに解凍されるようで、環境変数で設定する必要があります。同じものに対応する複数のバージョンの cudnn が必要な場合を除き、解凍されたフォルダーを cuda フォルダーに直接コピーするのが最も簡単です。 cudaバージョン。 

解凍後、include lib などのいくつかのフォルダーが存在するはずです。これらを対応する cuda ディレクトリに直接コピーするだけです。

Linux 側はもっと単純で、2 つの deb ファイルを直接インストールするだけですが、ここには順序関係があり、順番を間違えてインストールしてはいけません。

最初はランタイム ライブラリ、次に開発ライブラリ、ファイル名に dev が含まれているかどうかに注意してください、最後にサンプルとドキュメントがありますが、通常はインストールしません。

CMake 構成

 プロジェクトの作成方法、Linux や WSL ターゲットの追加方法など基本的なことは前回の記事を参照していただき、ここでは主に Win と Linux での CUDA のリンク位置とヘッダファイルの位置を設定する方法について説明します。

以下の CMakeLists.txt を参照してください。

cmake_minimum_required(VERSION 3.18)

project(cudnnBug)
if (UNIX)
	set(CUDA_TOOLKIT_ROOT_DIR "/usr/local/cuda")
	set(CMAKE_CUDA_COMPILER "${CUDA_TOOLKIT_ROOT_DIR}/bin/nvcc") #要在15行之前先设置Nvcc的路径
	set(CUDA_LIB_DIR "${CUDA_TOOLKIT_ROOT_DIR}/lib64")
elseif (WIN32)
    find_package(CUDA)
	set(CUDA_LIB_DIR "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64")
endif()
set(CUDA_INCLUDE "${CUDA_TOOLKIT_ROOT_DIR}/include")

project(${PROJECT_NAME} LANGUAGES CXX CUDA) #使能CUDA, 在这里会检查nvcc路径是否正确

FILE(GLOB SRCS "*.cpp" "*.cc" "*.cu") #将当前路径下所有的源文件写入SRCS , 如果需要搜索文件夹内的源文件则把GLOB_RECURSE替换GLOB
FILE(GLOB INCS "*.h" "*.hpp" "*.cuh") #同上,这里是头文件

add_executable(${PROJECT_NAME} ${INCS} ${SRCS})

set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61 70 75)#这个很重要, 这是编译的gpu代码支持的平台, 比如需要在1080TI运行就需要加上61
#另外CUDA_ARCHITECTURES好像是cmake 3.18的新特性,老的cmake 需要用 set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -gencode=arch=compute_70,code=sm_70")

target_include_directories(${PROJECT_NAME} PUBLIC ${CUDA_INCLUDE})

target_link_directories(${PROJECT_NAME} PUBLIC ${CUDA_LIB_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC cudart cudnn)

if(WIN32)
elseif(UNIX)
	target_link_libraries(${PROJECT_NAME} PUBLIC pthread)
endif()

上記のコメントは基本的にコアとなる知識ポイントを説明しており、グラフィックス カードに対応する CUDA_ARCHITECTURES をクエリする方法を追加する必要があります。

URLはこちら: https://developer.nvidia.com/cuda-GPUs

これで、Windows と Linux での CMake の構成が完了しました。

いくつかのテストコード

次のコードは cudnn の例です。これは私がかつて Nvidia に報告したバグです。

このコードは主に 3 つの畳み込み形式を示しています。

  1. 最初に cudnnConvolutionForward を使用して畳み込み、次に cudnnAddTensor を使用してバイアスを追加します
  2. cudnnConvolutionBiasActivationForward を直接使用して、CUDNN_ACTIVATION_IDENTITY をアクティベーションとして使用して、畳み込みとバイアスの重ね合わせを完了します。
  3. cudnnConvolutionBiasActivationForward を直接使用して、CUDNN_ACTIVATION_RELU をアクティベーションとして使用して、畳み込みとバイアスの重ね合わせを完了します。

本来であれば、1と2の結果は同じで、3にはレルの効果が発生するはずですが、バグの場合は2と3の両方にレルの効果が発生し、拡張率がそれよりも大きい場合に発生します。 3. NVIDIA からのフィードバックを受け、このバグは cuDNN8 で修正されましたが、cuDNN7 にも引き続き存在するはずです。

このプロジェクトには cudnn_convbug.cu が 1 つだけ含まれており、コードは次のとおりです。


#include "cuda_runtime.h"
#include "cudnn.h"
#include <stdio.h>
#include <vector>

#define CheckCUDNN(ret) \
{\
auto tmp = ret;\
if(tmp!=CUDNN_STATUS_SUCCESS)\
{\
  printf("CuDNN Error %d: %d",__LINE__,tmp);\
  return -1;\
}\
}

#define CheckCUDA(ret) \
{\
auto tmp=ret;\
if(tmp!=cudaSuccess)\
{\
  printf("CUDA Error %d: %d",__LINE__,tmp);\
  return -1;\
}\
}

static int cudnnBugTest(int dialation)
{
  constexpr int width = 28, height = 16;
  constexpr int bytesize = width * height * sizeof(float);
  int logsize = 100;
  
  cudnnHandle_t hcudnn = NULL;
  CheckCUDNN(cudnnCreate(&hcudnn));
  cudnnTensorDescriptor_t hinputtensor, houtputtensor, bias;
  cudnnCreateTensorDescriptor(&hinputtensor);
  cudnnCreateTensorDescriptor(&houtputtensor);
  cudnnCreateTensorDescriptor(&bias);
  cudnnSetTensor4dDescriptor(hinputtensor, cudnnTensorFormat_t::CUDNN_TENSOR_NCHW, cudnnDataType_t::CUDNN_DATA_FLOAT, 1, 1, height, width);
  cudnnSetTensor4dDescriptor(houtputtensor, cudnnTensorFormat_t::CUDNN_TENSOR_NCHW, cudnnDataType_t::CUDNN_DATA_FLOAT, 1, 3, height, width);
  cudnnSetTensor4dDescriptor(bias, cudnnTensorFormat_t::CUDNN_TENSOR_NCHW, cudnnDataType_t::CUDNN_DATA_FLOAT, 1, 3, 1, 1);
  float* src_dev, * tar_dev;
  float* src_h, * tar_h;
  float* bias_dev;
  float bias_h[] = { -100,-90,-80 };

  cudaMalloc(&src_dev, bytesize);
  cudaMalloc(&tar_dev, bytesize * 3);
  cudaMallocHost(&src_h, bytesize);
  cudaMallocHost(&tar_h, bytesize * 3);
  cudaMalloc(&bias_dev, 3 * sizeof(float));
  cudaMemcpy(bias_dev, bias_h, 3 * sizeof(float), cudaMemcpyKind::cudaMemcpyHostToDevice);
  for (int i = 0; i < height; i++)
  {
    for (int j = 0; j < width; j++)
    {
      *(src_h + i * width + j) = (float)(j);
    }
  }
  cudaMemcpy(src_dev, src_h, bytesize, cudaMemcpyKind::cudaMemcpyHostToDevice);
  float alpha(1), beta(0);

  cudnnConvolutionDescriptor_t hconv0;
  cudnnCreateConvolutionDescriptor(&hconv0);
  CheckCUDNN(cudnnSetConvolution2dDescriptor(hconv0, dialation, dialation, 1, 1, dialation, dialation, cudnnConvolutionMode_t::CUDNN_CROSS_CORRELATION, cudnnDataType_t::CUDNN_DATA_FLOAT));

  float* filter_h, * filter_d;
  constexpr int filtersizeb = 3 * 3 * 3 * sizeof(float);
  cudaMalloc(&filter_d, filtersizeb);
  cudaMallocHost(&filter_h, filtersizeb);
  for (int i = 0; i < 3 * 3 * 3; i++)
  {
    *(filter_h + i) = 1;
  }
  cudaMemcpy(filter_d, filter_h, filtersizeb, cudaMemcpyKind::cudaMemcpyHostToDevice);

  cudnnFilterDescriptor_t hfilter0;
  cudnnCreateFilterDescriptor(&hfilter0);
  CheckCUDNN(cudnnSetFilter4dDescriptor(hfilter0, cudnnDataType_t::CUDNN_DATA_FLOAT, cudnnTensorFormat_t::CUDNN_TENSOR_NCHW, 3, 1, 3, 3));
  size_t sizeInBytes = 0;
  void* workSpace = NULL;
  cudnnConvolutionFwdAlgo_t convalgo = cudnnConvolutionFwdAlgo_t::CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_PRECOMP_GEMM;
  CheckCUDNN(cudnnGetConvolutionForwardWorkspaceSize(hcudnn, hinputtensor, hfilter0, hconv0, houtputtensor, convalgo, &sizeInBytes));
  if (sizeInBytes)
  {
    cudaMalloc(&workSpace, sizeInBytes);
  }
  CheckCUDNN(cudnnConvolutionForward(hcudnn, &alpha, hinputtensor, src_dev, hfilter0, filter_d, hconv0
    , convalgo
    , workSpace, sizeInBytes, &beta, houtputtensor, tar_dev));
  cudnnAddTensor(hcudnn, &alpha, bias, bias_dev, &alpha, houtputtensor, tar_dev);
  std::vector<float> out0, out1, out2;
  out0.resize(width * height * 3);
  out1.resize(width * height * 3);
  out2.resize(width * height * 3);
  cudaMemcpy(out0.data(), tar_dev, bytesize * 3, cudaMemcpyKind::cudaMemcpyDeviceToHost);//这里tar_h是正确的计算结果
  
  cudnnActivationDescriptor_t act0;
  cudnnCreateActivationDescriptor(&act0);
  CheckCUDNN(cudnnSetActivationDescriptor(act0, cudnnActivationMode_t::CUDNN_ACTIVATION_IDENTITY, cudnnNanPropagation_t::CUDNN_NOT_PROPAGATE_NAN, 0));
  CheckCUDNN(cudnnConvolutionBiasActivationForward(hcudnn, &alpha, hinputtensor, src_dev, hfilter0, filter_d, hconv0
    , convalgo
    , workSpace, sizeInBytes, &beta, houtputtensor, tar_dev, bias, bias_dev, act0, houtputtensor, tar_dev));
  cudaMemcpy(out1.data(), tar_dev, bytesize * 3, cudaMemcpyKind::cudaMemcpyDeviceToHost);//这里tar_h是被RELU

  CheckCUDNN(cudnnSetActivationDescriptor(act0, cudnnActivationMode_t::CUDNN_ACTIVATION_RELU, cudnnNanPropagation_t::CUDNN_NOT_PROPAGATE_NAN, 10));
  CheckCUDNN(cudnnConvolutionBiasActivationForward(hcudnn, &alpha, hinputtensor, src_dev, hfilter0, filter_d, hconv0
    , convalgo
    , workSpace, sizeInBytes, &beta, houtputtensor, tar_dev, bias, bias_dev, act0, houtputtensor, tar_dev));
  cudaMemcpy(out2.data(), tar_dev, bytesize * 3, cudaMemcpyKind::cudaMemcpyDeviceToHost);//这里tar_h和CUDNN_ACTIVATION_IDENTITY的结果一样

  printf("Method0\tMethod1\tMethod2\n");
  for (size_t i = 0; i < logsize; i++)
  {
    printf("%f\t%f\t%f\n", out0[i], out1[i], out2[i]);
  }
  //TODO 清理cudaMalloc和cudaMallocHost的内存,由于只运行一次这里就暂时没加.
  CheckCUDNN(cudnnDestroy(hcudnn));
  return 1;
}

int main(int ac,const char**as)
{
    int dilation = 5;
    if (ac>1)
    {
        dilation = atoi(as[1]);
    }
    cudnnBugTest(dilation);
    CheckCUDA(cudaDeviceReset());
    return 0;
}

上記のプログラムは主に、膨張率を変更したときの 3 つの畳み込み結果の変化をテストするもので、動作は次のとおりです。

3 列のデータは上記の 3 つの畳み込み手法に対応しており、ある膨張率により 1 列目と 2 列目のデータが不等になる場合にバグが発生します。

エピローグ

この記事では、CUDA 構文と API については説明せず、Launch カーネルについても説明しません。この部分については、CUDA サンプルに直接アクセスすることをお勧めします。

私がお勧めするサンプルをいくつか紹介します。

 

おすすめ

転載: blog.csdn.net/luoyu510183/article/details/113471199