CUDA勉強記(1)
参考チュートリアル:
1. QINZHAOYU/CudaSteps
2. CUDA プログラミング (1) 基礎
3. CUDA C/C++ チュートリアル 1: アプリケーションの高速化
1. GPU ハードウェアおよび CUDA プログラム開発ツール
CPU と GPU で構成されるヘテロジニアス コンピューティング プラットフォームでは、通常、制御の役割を果たす CPU を と呼び主机(host)
、
加速の役割を果たす GPU を と呼びます设备(device)
。
ホストとデバイスの両方には独自の DRAM があり、通常は PCIe バスによって接続されます。
GPU の計算能力は計算パフォーマンスと同等ではありません。計算パフォーマンスを特徴付ける重要なパラメーターは、浮動小数点演算 (FLOPS) のピーク値です。
浮動小数点演算のピークには単精度および倍精度のポイントがあります。Tesla シリーズ GPU の場合、倍精度 FLOPS は通常、単精度の 1/2 です。GeForce シリーズ GPU の場合、倍精度 FLOPS は通常、単精度の 1/32 です。
CUDA 驱动API
CUDA は、とという 2 つの API 層を提供しますCUDA 运行时API
。
2. CUDA のスレッド構成
1. nvcc は C++ コードをコンパイルします
nvcc -o ./bin/hello_cu.exe hello.cu
./bin/hello_cu.exe
nvcc: hello world!
2. CUDA プログラムでのカーネル関数の使用
GPU を利用する CUDA プログラムには、ホスト コードとデバイス コードの両方が含まれます。
ホストからデバイスへの呼び出しは核函数
(カーネル関数)を通じて実現されます。
int main()
{
主机代码
核函数的调用
主机代码
return 0;
}
3. カーネル機能の特徴:
1)グローバル制限を追加する必要があります;
2) 戻り値の型は void 型 void である必要があります
3) カーネル関数は C++ の iostream をサポートしません
4) カーネル関数の呼び出しメソッド:
hello_from_gpu<<<1, 1>>> # <<<grid size, block size>>>
ホストがカーネル関数を呼び出すときは、デバイスに割り当てられているスレッドの数を示す必要があります。カーネル関数のスレッドは、多くの場合、いくつかのスレッド ブロックに編成されます:
A. 三重括弧内の最初の数字は线程块的个数
(スレッド ブロックの数)、
B. 三重括弧内の 2 番目の数字は每个线程块中的线程数
(ブロックごとのスレッドの数) です。
カーネル関数のすべてのスレッド ブロックはグリッド (グリッド) を形成し、スレッド ブロックの数はグリッド サイズ (グリッド サイズ) と呼ばれます。各スレッド ブロックには同じ数のスレッドが含まれており、この数はスレッド ブロック サイズ (ブロック サイズ).サイズ)。
したがって、カーネル関数のスレッドの総数は、グリッド サイズ * スレッド ブロック サイズになります。
カーネル関数を呼び出した後、CUDA ランタイム API 関数を呼び出してホストとデバイスを同期します 機能: デバイス コード (GPU) の実行が完了するのを待ってから、CPU 上で実行を続けます。
cudaDeviceSynchronize(); // 与其他并行化的代码类似,核函数启动方式为异步,即 CPU 代码将继续执行而不会等待核函数执行完成;但此行代码:可以让Host 代码(CPU) 等待 Device 代码(GPU) 执行完毕,再在CPU上继续执行。
4. CUDAのスレッド構成
4.1 CUDAのスレッド構成
GPU のすべてのコンピューティング リソースを完全に利用するには、カーネル関数のスレッドの総数がコンピューティング コアの数 (現在のコンピューターには 16 コア) 以上である必要があります。
hello_from_gpu<<<2, 4>>>
グリッド サイズは 2、スレッド ブロック サイズは 4、スレッドの合計数は 8 です。カーネル関数のコードの実行モードは、“单指令-多线程”
各スレッドが同じコード文字列を実行することです。
Kepler アーキテクチャ以降、許容される最大グリッド サイズは 2^31 - 1 (1 次元グリッド)、許容される最大スレッド ブロック サイズは 2^10 (1024) です。
スレッドの総数は、次の 2 つのパラメータによって決定できます。
- GridDim.x、グリッドのサイズ。
- blockDim.x、スレッドのブロック サイズ。
各スレッドの ID は、次の 2 つのパラメータによって決定できます。
- blockIdx.x、つまりグリッド内のスレッドのスレッド ブロック インデックス、[0,gridDm.x)。
- threadIdx.x、つまりスレッド ブロック内のスレッドのスレッド インデックス、[0, blockDim.x);
グリッドとスレッド ブロックはどちらも 3 次元構造に拡張できます (各軸のデフォルトは 1)。
- 3D グリッド Grid_size(gridDim.x, GridDim.y, GridDim.z);
- 3次元スレッドブロック block_size(blockDim.x, blockDim.y, blockDim.z);
同様に、3 次元構造の下では、各スレッドの識別パラメータは次のようになります。
- スレッド ブロック ID (blockIdx.x、blockIdx.y、blockIdx.z);
- スレッド ID (threadIdx.x、threadIdx.y、threadIdx.z);
スレッド ブロック上の多次元グリッド スレッドの ID:
tid = threadIdx.z * (blockDim.x * blockDim.y) // 当前线程块上前面的所有线程数
+ threadIdx.y * (blockDim.x) // 当前线程块上当前面上前面行的所有线程数
+ threadIdx.x // 当前线程块上当前面上当前行的线程数
グリッド上の多次元グリッド スレッド ブロックの ID:
bid = blockIdx.z * (gridDim.x * gridDim.y)
+ blockIdx.y * (gridDim.x)
+ blockIdx.x
スレッド ブロック内のスレッドは、それぞれが 32 個の連続したスレッドで構成される、異なるスレッド ワープに編成することもできます。
Kepler アーキテクチャから Turing アーキテクチャまでの GPU の場合、x、y、z 方向のグリッド サイズの最大許容値、スレッドを必要とする場合の x、y (2^31 - 1, 2^16 - 1, 2^16 -1)
、z 方向のスレッド ブロック サイズの最大許容値(1024, 1024, 64)
ブロック 最大でも1024
スレッドが存在します。
展開:
<<<>>> 演算子は、カーネル関数の実行設定パラメータ形式を完了します。<<<Dg, Db, Ns, S>>>
1) パラメータ Dg は、グリッド全体の寸法とサイズ、つまりグリッドに含まれるブロックの数を定義するために使用されます。dim3タイプです。Dim3 Dg(Dg.x, Dg.y, 1) は、グリッドの各行に Dg.x ブロックがあり、各列に Dg.y ブロックがあり、3 番目の次元は常に 1 であることを意味します (現在、カーネル関数には 1 つのみがあります)グリッド)。グリッド全体にDg.x Dg.y ブロックがあり、Dg.x と Dg.y の最大値は 65535 です。
2) パラメータ Db は、ブロックの次元とサイズ、つまりブロックに含まれるスレッドの数を定義するために使用されます。dim3タイプです。Dim3 Db(Db.x, Db.y, Db.z) は、ブロック全体の各行に Db.x スレッドがあり、各列に Db.y スレッドがあり、高さが Db.z であることを意味します。Db.x と Db.y の最大値は 512、Db.z の最大値は 62 です。ブロック内にはDb.x Db.y*Db.z スレッドがあります。この製品の最大値は、演算能力 1.0 および 1.1 のハードウェアで 768 であり、演算能力 1.2 および 1.3 のハードウェアでサポートされる最大値は 1024 です。
3) パラメータ Ns は、静的に割り当てられた共有メモリを除く各ブロックの最大値を設定するオプションのパラメータであり、能动态分配的shared memory大小
単位はバイトです。動的割り当てが不要な場合は0または省略されます。
4) パラメータ S はcudaStream_t类型
オプションのパラメータで、初期値は 0 で、カーネル関数がどのストリームにあるかを示します。
4.2 CUDA ヘッダー ファイル
CUDA ヘッダー ファイルのサフィックスは通常「.cuh」ですが、
同時に c/cpp ヘッダー ファイル「.h」および「.hpp」をインクルードすることができ、nvcc コンパイラーは必要な cuda ヘッダー ファイルを自動的にインクルードします
。 <cuda.h >、<cuda_runtime.h> として、前者には C++ ヘッダー ファイル <stdlib.h> も含まれます。
4.3 nvcc を使用して CUDA プログラムをコンパイルする
主机代码
nvcc は最初にすべてのソース コードをとに分割します设备代码
。
ホスト コードは C++ 構文を完全にサポートしますが、デバイス コードは部分的にのみサポートします。
コンパイル プロセス:
nvcc は、まずデバイス コードを PTX (並列スレッド実行) 擬似アセンブリ コードにコンパイルし、次にそれをバイナリ cubin オブジェクト コードにコンパイルします。
PTX コードにコンパイルする場合は、-arch=compute_XY
仮想アーキテクチャのコンピューティング能力を指定するオプションが必要です。cubin コードにコンパイルする場合、-code=sm_ZW
実行可能ファイルが使用できる GPU を決定するために、実際のアーキテクチャのコンピューティング能力を指定するオプションが必要です。
実際のアーキテクチャの計算能力は、仮想アーキテクチャの計算能力以上である必要があります。次に例を示します。
-arch=compute_35 -code=sm_60 (right)
-arch=compute_60 -code=sm_35 (wrong)
4.4 グラフィックス カードのアーキテクチャと計算能力
Jetson Orin モジュールには次のものが含まれます:
最大 2048 個の CUDA コアと最大 64 個の Tensor コアを備えた NVIDIA Ampere アーキテクチャ GPU
アンペア (CUDA 11 ~現在) SM80 または SM_80、compute_80 – NVIDIA A100 (Tesla という名前ではなくなりました – GA100)、NVIDIA DGX-A100 SM86
または SM_86、compute_86 – (CUDA 11.1 以降) Tesla GA10x、RTX アンペア – RTX 3080 、GA102 – RTX 3090、RTX A6000、RTX A40
3. 単純な CUDA プログラムの基本フレームワーク
1. 単一のソース ファイルを持つ cuda プログラムの場合、基本的なフレームワークは次のとおりです。
包含头文件
定义常量或宏
声明 c++ 自定义函数和 cuda 核函数的原型
int main()
{
1. 分配主机和设备内存
2. 初始化主机中数据
3. 将某些数据从主机复制到设备
4. 调用核函数在设备中计算
5. 将某些数据从设备复制到主机
6. 释放主机和设备内存
}
2. CUDA カーネル機能の要件:
1) 戻り値の型は void である必要がありますが、関数内で return を使用できます (ただし、値を返すことはできません); 2
) glolbal修飾子を使用する必要があり、C++ 修飾子も追加できます;
3) コア関数は、 C++ のオーバーロード メカニズム;
4) カーネル関数は、可変数のパラメータ リストをサポートしていません。つまり、パラメータの数を決定する必要があります; 5) 一般に、
カーネル関数に渡される配列 (ポインタ) は、デバイス メモリ (「統合メモリ プログラミング メカニズム」を除く);
6 ) 核函数不可成为一个类的成员
(通常は次で終わります)ラッパー関数がカーネル関数を呼び出す、ラッパー関数をクラス メンバーとして定義します);
7) コンピューティング機能 3.5 より前では、カーネル関数は相互に呼び出すことができません; それ以降は、「動的並列」メカニズムを通じて呼び出すことができます; 8) ホスト
から呼び出されるか、またはホストから呼び出されるかデバイス、カーネル関数はデバイス内で実行されます ("<<<,>>>" は実行構成を指定します)。
3. カスタムデバイス機能
カーネル関数は、実行構成なしでカスタム関数、つまりデバイス関数を呼び出すことができます。
デバイス関数はデバイスで実行され、デバイス (gpu) で呼び出されますが、カーネル関数はデバイスで実行され、ホスト (cpu) で呼び出されます。
文法規則:
1)__global__
変更された関数はカーネル関数と呼ばれ、通常はホストによって呼び出され、デバイスで実行されます;
2)__device__
変更された関数はデバイス関数と呼ばれ、カーネル関数または他のデバイス関数によってのみ呼び出され、デバイスで実行されます。デバイス;
3)__host__
ホスト セグメントの通常の C++ 関数を変更するため、ホスト内で呼び出され実行され、通常は省略できます; 4)関数を同時に変更する
ために使用できるため、コードの冗長性が削減されます。このとき、コンパイラは 5) ホストでそれぞれ関数をコンパイルし、デバイスで関数をコンパイルします; 6)関数と関数を同時に使用することはできません; 7)関数と関数を同時に使用することはできません; 9)コンパイラがデバイス関数をインライン関数として扱わないことを示唆するために使用できます; 10)コンパイラがデバイス関数をインライン関数として扱うことを示唆するために使用できます。11) デバイス関数は戻り値を持つことができます。__host__
__device__
__global__
__device__
__global__
__host__
__noinline__
__forceinline__