ラベル CUDA の投稿を表示しています。 すべての投稿を表示
ラベル CUDA の投稿を表示しています。 すべての投稿を表示

2019/09/26

CUDAの導入方法(Ubuntu編)

背景


GPUで並列処理を行うために、CUDAの導入が必要となった。

記事の目的


UbuntuにCUDAを導入する

CUDA


ここでは、CUDAの導入方法について記載する。

CUDAとは

CUDAは、nvidia社が提供する並列処理ライブラリである。

利点

  • 実装に関する情報が豊富である
  • TensorflowやOpenCVなど、機械学習や画像処理に関するツールはが基本的に対応している
  • C言語ライクな記述でプログラミングできる

導入方法

OSやマシンにあったCUDAのインストーラ方法は、公式ページから調べることができる。
今回は、Ubuntu16.04へCUDAを導入する手順について記載する。
  1. CUDAのアーカイブをダウンロードし、aptに登録する
  2. $ wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-ubuntu1604.pin
    $ sudo mv cuda-ubuntu1604.pin /etc/apt/preferences.d/cuda-repository-pin-600
  3. OSやマシンにあった承認キーをダウンロードページから探す(今回は、/ubuntu1604/x86_64/7fa2af80.pub)
  4. 承認キーをダウンロードする
  5. $ sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
  6. CUDAのレポジトリを登録する
  7. $ sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/ /"
  8. CUDAをインストールする
  9. $ sudo apt-get update
    $ sudo apt-get -y install cuda
  10. PCを再起動する
  11. $ sudo reboot

CUDAのサンプルプログラム

CUDAのサンプルプログラムのmake方法は、下記の通りである。
$ /usr/local/cuda-10.1/bin/cuda-install-samples-10.1.sh ~
$ cd ~/NVIDIA_CUDA-10.1_Samples
$ make
上記の場合、~/NVIDIA_CUDA-10.1_Samples内にサンプルプログラムの実行ファイルが作成される。

アンインストール方法

CUDAのアンインストール方法について記載する。
$ sudo apt remove cuda-10-1
$ sudo apt autoremove
$ sudo apt remove libcudnn7 libcudnn7-dev libcudnn7-doc
$ rm -rfv ~/NVIDIA_CUDA-10.1_Samples/

まとめ


  • UbuntuにCUDAを導入する方法ついて調査、記載した

参考文献



変更履歴


  1. 2019/09/26: 新規作成

2019/08/26

forループをCUDAで並列化する方法

背景


CUDAを利用して並列化を行い、処理を高速化する必要が出てきたため、CUDAで並列化処理を記述する方法について記述する

記事の目的


CUDAで並列化処理を行う際のテンプレートを作成する

CUDA


ここでは、CUDAを利用したCプログラムの記述方法について記載する。

CUDAとは

CUDAは、nvidia社が提供するGPUを利用した並列演算プログラミング基盤である

利点

  • ユーザーが多く、情報も入手しやすい
  • ライブラリが充実している
  • 導入が容易である

テンプレート

CUDAを利用して並列処理を行うテンプレートを記載する。
// cuda_sample_code.cu
#include <stdio.h>

// デバイス(GPU)側のスレッドの設定 //////////////////////////////////
// BLOCK_SIZE_Xは、1ブロックあたりのスレッド数を表す
// GPUの種類により、1ブロックあたりのスレッド数の制限が異なる
// 最適なスレッド数を設定しないと、カーネル関数の処理がスキップされる
// 注)上記の場合、エラーでプロセスが落ちる事はない
#define BLOCK_SIZE_X 512

// カーネル関数(GPUで処理する関数)vec_sumの宣言 /////////////////////
// この関数がGPUで並列実行される
__global__
void vec_sum(float k, float *a, float *b, float *c)
{
    // iは、"for(int i=0; i < grid.x * block.x; i++)" の値を取る
    // ただし、並列処理のため、i++順に処理されるわけではない
    int i = blockIdx.x*blockDim.x + threadIdx.x;
    c[i] = k*a[i] + b[i];

    // GPU内のスレッドの同期 ////////////////////////////////////////
    // block_size * grid_size個の全てのスレッドが、ここで同期する
    __syncthreads();
}

// カーネル関数を呼び出す関数を宣言 /////////////////////////////////
void cuda_handler(float *a, float *b, float *c)
{
    // デバイス用のポインタを宣言 ///////////////////////////////////
    float *d_a, *d_b, *d_c;

    // デバイス(GPU)側の配列のメモリを確保 //////////////////////////
    // デバイス側のメモリ確保処理は重いため、回数は減らした方が良い
    cudaMalloc(&d_a, N*sizeof(float));
    cudaMalloc(&d_b, N*sizeof(float));
    cudaMalloc(&d_c, N*sizeof(float));

    // ホスト側の配列内のデータをデバイス側にコピー /////////////////
    cudaMemcpy(d_a, a, N*sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, b, N*sizeof(float), cudaMemcpyHostToDevice);

    // デバイス側の配列cの全ての要素に0をセット /////////////////////
    cudaMemset(d_c, 0, N*sizeof(float));

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、メモリへのデータセットのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());

    // 並列処理するスレッド数を定義 /////////////////////////////////
    // 総スレッド数は block_size * grid_size = N 個である
    // x, y, zの3次元まで設定可能である
    dim3 block_size (BLOCK_SIZE_X, 1, 1);
    dim3 grid_size  (N / block.x, 1, 1);

    // カーネル関数(GPUで処理する関数)の呼び出し ////////////////////
    // カーネル関数内では、デバイス側のメモリ内のデータのみ操作可能
    vec_sum<<<grid_size, block_size>>>(2.0f, d_a, d_b, d_c);

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、vec_sumのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());

    // この行までに実行されたカーネル関数の処理が完了するまで待機 ///
    // デバイスとホストの処理は非同期である
    // 同期処理を行うか、cudaMemcpyするまで、互いは独立して動作する
    cudaThreadSynchronize();

    // 計算結果をホスト側にコピー ///////////////////////////////////
    cudaMemcpy(c, d_c, N*sizeof(float), cudaMemcpyDeviceToHost);

    // デバイス(GPU)側の配列のメモリを開放 //////////////////////////
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);

    // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
    // ここでは、メモリへのデータセットのエラーチェックに利用している
    // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
    // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
    checkCudaErrors(cudaGetLastError());
}

int main(void)
{
    // 計算回数の設定 ///////////////////////////////////////////////
    // N = 512×2048
    int N = 1<<20;

    // ホスト用のポインタを宣言 /////////////////////////////////////
    float *a, *b, *c;

    // ホスト側の配列のメモリを確保 /////////////////////////////////
    a = (float*)malloc(N*sizeof(float));
    b = (float*)malloc(N*sizeof(float));
    c = (float*)malloc(N*sizeof(float));

    // a, bの配列にそれぞれ1,2を代入////////////////////////////////
    for (int i = 0; i < N; i++) {
        a[i] = 1.0f;
        b[i] = 2.0f;
    }

    // cudaでの処理を行う関数 ///////////////////////////////////////
    cuda_handler(a, b, c);

    // 計算結果の確認 ///////////////////////////////////////////////
    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
    {
        maxError = max(maxError, abs(c[i]-4.0f));
    }
    printf("Max error: %f", maxError);

    // ホスト側の配列のメモリを開放 /////////////////////////////////
    free(a);
    free(b);
    free(c);

    return 0;
}

テンプレートのコンパイル方法

テンプレートのコンパイル方法を記載する。
nvcc -lcuda ./cuda_sample_code.cu -o ./cuda_sample
デバッグログを出力する場合は、下記のように記載する。
nvcc -lcuda -g -G ./cuda_sample_code.cu -o ./cuda_sample

テンプレートの実行結果

テンプレートの実行結果を記載する。
$ ./cuda_sample
Max error: 0.000000

まとめ


  • CUDAでプログラミングをする際のテンプレートを調査、記載した

参考文献



変更履歴


  1. 2019/08/26: 新規作成

2019/08/25

make時にGPUのアーキテクチャ番号を自動設定させる方法

背景


複数種類の開発環境でまたがってCUDAを利用したプログラムを開発する際、マシンに搭載されたGPUのアーキテクチャに応じて、CMakeLists.txt内のアーキテクチャ番号を毎回書き換える必要があった。メンテナンスの効率化のため、どのGPU搭載のマシンでmakeした場合でも、GPUの種類を識別して、そのGPUに最適なバイナリファイルを生成する環境を構築する。

記事の目的


makeする際に自動で搭載GPUのアーキテクチャを識別し、そのGPUに最適なバイナリーを出力するようにmake環境を構築する

仮想/実アーキテクチャの設定


ここでは、UbuntuにおけるマルチGPU対応のmake環境を構築するための方法について記載する。

nvccのコンパイルオプション

nvccのコンパイルオプションのうち、アーキテクチャに関する部分について記述する。
  • --gpu-architecture (-arch)
  • *.cuが対象にしている仮想アーキテクチャを指定する。したがって、基本的にはcompute_*の中から選択する。
  • --gpu-code (-code)
  • 仮想アーキテクチャのコード(*.ptx)から生成して出力に加えるアーキテクチャを指定する。PTXを含めたいときはcompute_*、特定のGPU向けバイナリを含めたいときはsm_*を選択する。
  • --generate-code arch=compute_*,code=\"compute_*,sm_*\"
  • 上記2つのオプションを一度に設定できる

導入方法

make環境を構築する手順は、下記の通りである。
  1. check_cuda.cuをCMakeLists.txtと同じ階層に置く
  2. // check_cuda.cu
    #include <stdio.h>
    
    int main(int argc, char **argv){
        cudaDeviceProp dP;
        float min_cc = 3.0;
    
        int rc = cudaGetDeviceProperties(&dP, 0);
        if(rc != cudaSuccess) {
            cudaError_t error = cudaGetLastError();
            printf("CUDA error: %s", cudaGetErrorString(error));
            return rc; /* Failure */
        }
        if((dP.major+(dP.minor/10)) < min_cc) {
            printf("Min Compute Capability of %2.1f required:  %d.%d found",
                    min_cc, dP.major, dP.minor);
            printf(" Not Building CUDA Code");
            return 1; /* Failure */
        } else {
            printf("%d%d", dP.major, dP.minor);
            return 0; /* Success */
        }
    }
  3. CMakeLists.txtを編集する
  4. 下記のコードを追加することで、$CUDA_NVCC_FLAGSにコンパイルしたマシンのアーキテクチャを自動設定できる。
    cmake_minimum_required(VERSION 3.0)
    # Find CUDA
    find_package(CUDA)
    
    if (CUDA_FOUND)
      #Get CUDA compute capability
      set(OUTPUTFILE ${CMAKE_CURRENT_SOURCE_DIR}/cuda_script) # No suffix required
      set(CUDAFILE ${CMAKE_CURRENT_SOURCE_DIR}/check_cuda.cu)
      execute_process(COMMAND nvcc -lcuda ${CUDAFILE} -o ${OUTPUTFILE})
      execute_process(COMMAND ${OUTPUTFILE}
                      RESULT_VARIABLE CUDA_RETURN_CODE
                      OUTPUT_VARIABLE ARCH)
      execute_process(COMMAND rm ${OUTPUTFILE})
    
      if(${CUDA_RETURN_CODE} EQUAL 0)
        set(CUDA_SUCCESS "TRUE")
      else()
        set(CUDA_SUCCESS "FALSE")
      endif()
    
      if (${CUDA_SUCCESS})
        message(STATUS "CUDA Architecture: -arch=sm_${ARCH}")
        message(STATUS "CUDA Version: ${CUDA_VERSION_STRING}")
        message(STATUS "CUDA Path: ${CUDA_TOOLKIT_ROOT_DIR}")
        message(STATUS "CUDA Libararies: ${CUDA_LIBRARIES}")
        message(STATUS "CUDA Performance Primitives: ${CUDA_npp_LIBRARY}")
    
        set(CUDA_NVCC_FLAGS "{$CUDA_NVCC_FLAGS};--generate-code arch=compute_${ARCH},code=\"compute_${ARCH},sm_${ARCH}\"")
    
      else()
        message(WARNING -arch=sm_${ARCH})
      endif()
    endif()

まとめ


  • 搭載GPUのアーキテクチャを識別し、そのGPUに最適なバイナリーを出力するmake環境を構築する手順について調査、記載した

参考文献



変更履歴


  1. 2019/08/25: 新規作成

2019/08/24

CUDA10のコンパイラでrosのパッケージがコンパイルできない問題の対処法

背景


CUDA10のnvccを用いてrosのパッケージをコンパイルする際、エラーが発生してコンパイルできない問題が生じた。

記事の目的


CUDA10のnvccでrosパッケージをコンパイルできるようにする

nvccのバグ


ここでは、CUDA10のnvccでrosパッケージをコンパイルできない現象、原因及び対策について記載する。

現象

CUDA10のnvccでrosパッケージをコンパイルすると、下記のエラーが発生する場合がある。
$ catkin_make
...
/usr/include/pcl-1.7/pcl/point_cloud.h:586:100 error: template-id ‘getMapping’ used as a declarator
friend boost::shared_ptr& detail::getMapping(pcl::PointCloud &p);
^
/usr/include/pcl-1.7/pcl/point_cloud.h:586:100 error: ‘getMapping’ is neither function nor member function; cannot be declared friend
cc1plus: error: expected ‘;’ at end of member declaration
/usr/include/pcl-1.7/pcl/point_cloud.h:586:111: error: expected ‘)’ before ‘&’ token
...
make: *** [all] Error 2

原因

nvccが利用しているgnuコンパイラのバグが原因である。このバグは、「friend関数によりnamespace内で定義された関数を使用する」記述があるコードで発生する。ところで、rosは独自に手を加えたPointCloudLibrary(PCL)を使用しているが、PCLのコード内に上記の記述が存在する('/usr/include/pcl-1.7/pcl/point_cloud.h'内 l.586)。nvccのコンパイラは、上記箇所をコンパイルする際に、関数のnamespace部分を削除してしまう(detail::getMapping→getMapping)。そのため、コンパイラ自身が関数を見つけられなくなり、エラーとなる。
$less /usr/include/pcl-1.7/pcl/point_cloud.h 
'point_cloud.h'内の該当箇所
 
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
  } // namespace detail
 
    protected:
      // This is motivated by ROS integration. Users should not need to access mapping_.
      boost::shared_ptr<MsgFieldMap> mapping_;

      friend boost::shared_ptr<MsgFieldMap>& detail::getMapping<PointT>(pcl::PointCloud<PointT> &p);

    public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  };

  namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
    {
      return (p.mapping_);
    }
  } // namespace detail

対策

'/usr/include/pcl-1.7/pcl/point_cloud.h'を下記のように書き換えることで回避できる。
// Add ---------------------------------------------------------------------- //
template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
// -------------------------------------------------------------------------- //
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p);
  } // namespace detail
 
    protected:
      // This is motivated by ROS integration. Users should not need to access mapping_.
      boost::shared_ptr<MsgFieldMap> mapping_;
      // Change ------------------------------------------------------------------- //
      friend boost::shared_ptr<MsgFieldMap>& getMapping<PointT>(pcl::PointCloud<PointT> &p);
      // friend boost::shared_ptr<MsgFieldMap>& detail::getMapping<PointT>(pcl::PointCloud<PointT> &p);
      // -------------------------------------------------------------------------- //
    public:
      EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  };

// Add ---------------------------------------------------------------------- //
template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
{
    return (p.mapping_);
}
// -------------------------------------------------------------------------- //
namespace detail
  {
    template <typename PointT> boost::shared_ptr<pcl::MsgFieldMap>&
    getMapping (pcl::PointCloud<PointT>& p)
    {
      return (p.mapping_);
    }
  } // namespace detail

備考

  • ros-kinetic、cuda 10.1で検証した
  • コンパイルが通ることと、実行可能であることを確認した

まとめ


  • rosをapt install時にインストールされるpclのヘッダーファイルを変更することにより、CUDA10のnvccでrosパッケージがコンパイルできなくなる問題を回避した

参考文献



変更履歴


  1. 2019/08/24: 新規作成

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...