OpenCLメモ (その2) - Intel GPUの構造
The Compute Architecture of Intel Processor Graphics Gen9と、2014 OpenCL Optimization Guideをたよりに、Intel GPUの構造を確認した。
構造を理解していないと、いろいろな罠にひっかかって速度が出ないというのが、GPUの特徴である。
GPUのおおまかな構造はこんな感じ。i7 6700Kほか多くのCPUの内蔵GPUであるHD Graphics 530はGT2とよばれる規模のGPUで、24EUと言われる。この24EUのまとまりをsliceと呼び、これがリングバスを通してメモリコントローラにつながっている。
実際には1つのslice中は3つのsubsliceに分かれていて、1subsliceあたり8EUを搭載することで、24EUとなっている。
EUはいくつかの演算器の集合で、
・128bit SIMD演算器(ALU/FPU) x2
・Sendユニットx1 (メモリアクセスとかEUの外にアクセスする処理全般)
・Branchユニットx1
・256bitレジスタ x128 x 7スレッド分
を持つ。
2基の128bit SIMD演算器を合わせると、以下のような計算のいずれかを毎cycle発行可能である。
・16 x FP16
・8 x FP32
・2 x FP64 (片方のユニットしか実行できない)
・16 x INT16
・8 x INT32
EUでの計算は基本的にSIMD演算器を使うことになるので、命令もそれにそってSIMD型の命令となっている。コンパイラは、プログラムに応じて、8 / 16 / 32 のwork itemを1つのスレッドにまとめて実行する。(SIMD-8 / SIMD-16 / SIMD-32)
実際には、SIMD-32の演算器は存在しないので、例えばSIMD-8 x4などに分解されて実行される。
EUは最大7スレッドを(切り替えながら)同時に処理することができる。なるべく多くのスレッドをEUに突っ込むことで、演算器の使用率を高く保ち、演算効率を上げることができる。
8つのEUが集まってsubsliceを構成する。subsliceには8つのEUからのデータアクセスを処理するData Portが存在するが、読み書き双方向にそれぞれ64byte/cycleであり、EU8つの演算力と比べれば十分なデータ供給力があるとはいえず、なるべくEU内のレジスタを使用した演算を行うことが効率よく計算するためには必要であることがわかる。
ただし、Data Port以外にOpenCLのimageNdを使用した場合のみ使えるアクセス方法が存在し、これがいわゆるSamplerキャッシュ(テクスチャキャッシュ)である。これは読み込み専用であるが、64byte/cycleの帯域と4KBのL1キャッシュ + 24KBのL2キャッシュを持つ。
Data PortはEUからのメモリアクセスをまとめて効率化することが可能である。これをコアレスアクセスという。例えば、SIMD-16などであれば、そのスレッドは16work itemについて一度に処理するわけだが、その16work itemのメモリアクセスが1つのキャッシュライン(64byte以内)であれば、これをまとめてアクセスするすることで効率よくアクセスするというものである。
GPU演算でメモリアクセスの効率を上げることは非常に重要なため、連続するwork itemのメモリアクセスが必要とするキャッシュラインをなるべく少なくし、取り込んだデータに無駄がないようにする必要がある。基本的にメモリアクセスはキャッシュライン(64byteの塊)を単位として行われるので、これを意識すると効率が良くなる。
例1のように、連続するwork itemのアクセスが、ひとつのキャッシュラインに収まっていれば、1回のメモリアクセスですみ、効率が良い。
例1
例2のように、飛び飛びのアクセスであると、複数のキュッシュラインからデータを取ってくることになり、アクセス効率が低下する。また、読み込んでも使用していないデータが多く、無駄にメモリ帯域を使っていることになる。
例2
連続であればなんでも良いかというと、そういうわけではなく、先頭のアドレスがキャッシュラインとずれていると、キャッシュラインをまたぐことになよって、2回のメモリアクセスが必要になり、効率が悪化する。
例3
同じキャッシュラインへのアクセスであれば問題ないので、例4のように、逆順でも問題はない。
例4
このように、先頭のwork itemのアクセス先のアライメントをとり、work itemが連続したアクセスとすることで、なるべくキャシュラインをまたがないようにして、メモリアクセス効率を高めることが重要である。
最後に3つのsubsliceとL3キャッシュ(512KB)が1つのsliceを形成する。Data PortからのアクセスやSamplerからのアクセスはすべてこのL3キャッシュを通り、64byte/cycleでリングバスに接続される。
Shared Local Memoryはsubsliceあたり64KB(計192KB)まで利用可能な特殊なメモリ領域で、work group内で共有して利用することができる。また、通常のメモリアクセスよりも比較的多様なアクセスパターンを高速に処理できるという特徴を持つ。そのため、work group内のwork itemが、他のwork itemの計算結果を使用したい場合、このSLMを経由して渡してやると効率的にやり取りできる。また、work group内のwork itemが同じ値を何度も重複して利用する場合に、明示的にSLMにキャシュしてやると、高速化につながることがある。
ただ、このSLMがsubsliceの外にあるため、必ずDataPortを経由せざるを得ず、EUからのアクセス帯域に制限が生じていて、そこまで高速というわけではないので、注意が必要である。SLMはwork group内で共有すればよく、同じWork groupのwork itemは必ず同じsubsliceで実行されるのだとすれば、SLMはsubslice内にあればよいはずで、なぜSLMをsubsliceの外に実装しているのかが、Intelの実装で不思議な点である。(例えば、NVIDIA PascalのsharedメモリはSMの中にある)
ちなみに、sliceが1つなのはHD Graphics 530が24EUのGT2だからで、Skylakeにはさらに48EUのGT3、72EUのGT4があり、この場合はsliceの数が2、3と増えることになる。
というわけでざっと資料を見るとこんな感じ。
やはり、性能を出すにはwork groupの構成とか、work itemのメモリアクセスとか、そのあたりを注意する必要があるように思う。
続き>>
OpenCLメモリスト
OpenCLメモ (その1) - かんたんな計算
OpenCLメモ (その2) - Intel GPUの構造 (いまここ)
OpenCLメモ (その3) - work sizeの調整
OpenCLメモ (その4) - 転送(コピー)の排除 (USE_HOST_PTR)
OpenCLメモ (その5) - 転送(コピー)の排除 (SVM : OpenCL 2.0)
OpenCLメモ (その6) - imageの使用
OpenCLメモ (その7) [終] - reductionとshared local memory(SLM)の使用
構造を理解していないと、いろいろな罠にひっかかって速度が出ないというのが、GPUの特徴である。
GPUのおおまかな構造はこんな感じ。i7 6700Kほか多くのCPUの内蔵GPUであるHD Graphics 530はGT2とよばれる規模のGPUで、24EUと言われる。この24EUのまとまりをsliceと呼び、これがリングバスを通してメモリコントローラにつながっている。
実際には1つのslice中は3つのsubsliceに分かれていて、1subsliceあたり8EUを搭載することで、24EUとなっている。
EUはいくつかの演算器の集合で、
・128bit SIMD演算器(ALU/FPU) x2
・Sendユニットx1 (メモリアクセスとかEUの外にアクセスする処理全般)
・Branchユニットx1
・256bitレジスタ x128 x 7スレッド分
を持つ。
2基の128bit SIMD演算器を合わせると、以下のような計算のいずれかを毎cycle発行可能である。
・16 x FP16
・8 x FP32
・2 x FP64 (片方のユニットしか実行できない)
・16 x INT16
・8 x INT32
EUでの計算は基本的にSIMD演算器を使うことになるので、命令もそれにそってSIMD型の命令となっている。コンパイラは、プログラムに応じて、8 / 16 / 32 のwork itemを1つのスレッドにまとめて実行する。(SIMD-8 / SIMD-16 / SIMD-32)
実際には、SIMD-32の演算器は存在しないので、例えばSIMD-8 x4などに分解されて実行される。
EUは最大7スレッドを(切り替えながら)同時に処理することができる。なるべく多くのスレッドをEUに突っ込むことで、演算器の使用率を高く保ち、演算効率を上げることができる。
8つのEUが集まってsubsliceを構成する。subsliceには8つのEUからのデータアクセスを処理するData Portが存在するが、読み書き双方向にそれぞれ64byte/cycleであり、EU8つの演算力と比べれば十分なデータ供給力があるとはいえず、なるべくEU内のレジスタを使用した演算を行うことが効率よく計算するためには必要であることがわかる。
ただし、Data Port以外にOpenCLのimageNdを使用した場合のみ使えるアクセス方法が存在し、これがいわゆるSamplerキャッシュ(テクスチャキャッシュ)である。これは読み込み専用であるが、64byte/cycleの帯域と4KBのL1キャッシュ + 24KBのL2キャッシュを持つ。
Data PortはEUからのメモリアクセスをまとめて効率化することが可能である。これをコアレスアクセスという。例えば、SIMD-16などであれば、そのスレッドは16work itemについて一度に処理するわけだが、その16work itemのメモリアクセスが1つのキャッシュライン(64byte以内)であれば、これをまとめてアクセスするすることで効率よくアクセスするというものである。
GPU演算でメモリアクセスの効率を上げることは非常に重要なため、連続するwork itemのメモリアクセスが必要とするキャッシュラインをなるべく少なくし、取り込んだデータに無駄がないようにする必要がある。基本的にメモリアクセスはキャッシュライン(64byteの塊)を単位として行われるので、これを意識すると効率が良くなる。
例1のように、連続するwork itemのアクセスが、ひとつのキャッシュラインに収まっていれば、1回のメモリアクセスですみ、効率が良い。
例1
例2のように、飛び飛びのアクセスであると、複数のキュッシュラインからデータを取ってくることになり、アクセス効率が低下する。また、読み込んでも使用していないデータが多く、無駄にメモリ帯域を使っていることになる。
例2
連続であればなんでも良いかというと、そういうわけではなく、先頭のアドレスがキャッシュラインとずれていると、キャッシュラインをまたぐことになよって、2回のメモリアクセスが必要になり、効率が悪化する。
例3
同じキャッシュラインへのアクセスであれば問題ないので、例4のように、逆順でも問題はない。
例4
このように、先頭のwork itemのアクセス先のアライメントをとり、work itemが連続したアクセスとすることで、なるべくキャシュラインをまたがないようにして、メモリアクセス効率を高めることが重要である。
最後に3つのsubsliceとL3キャッシュ(512KB)が1つのsliceを形成する。Data PortからのアクセスやSamplerからのアクセスはすべてこのL3キャッシュを通り、64byte/cycleでリングバスに接続される。
Shared Local Memoryはsubsliceあたり64KB(計192KB)まで利用可能な特殊なメモリ領域で、work group内で共有して利用することができる。また、通常のメモリアクセスよりも比較的多様なアクセスパターンを高速に処理できるという特徴を持つ。そのため、work group内のwork itemが、他のwork itemの計算結果を使用したい場合、このSLMを経由して渡してやると効率的にやり取りできる。また、work group内のwork itemが同じ値を何度も重複して利用する場合に、明示的にSLMにキャシュしてやると、高速化につながることがある。
ただ、このSLMがsubsliceの外にあるため、必ずDataPortを経由せざるを得ず、EUからのアクセス帯域に制限が生じていて、そこまで高速というわけではないので、注意が必要である。SLMはwork group内で共有すればよく、同じWork groupのwork itemは必ず同じsubsliceで実行されるのだとすれば、SLMはsubslice内にあればよいはずで、なぜSLMをsubsliceの外に実装しているのかが、Intelの実装で不思議な点である。(例えば、NVIDIA PascalのsharedメモリはSMの中にある)
ちなみに、sliceが1つなのはHD Graphics 530が24EUのGT2だからで、Skylakeにはさらに48EUのGT3、72EUのGT4があり、この場合はsliceの数が2、3と増えることになる。
というわけでざっと資料を見るとこんな感じ。
やはり、性能を出すにはwork groupの構成とか、work itemのメモリアクセスとか、そのあたりを注意する必要があるように思う。
続き>>
OpenCLメモリスト
OpenCLメモ (その1) - かんたんな計算
OpenCLメモ (その2) - Intel GPUの構造 (いまここ)
OpenCLメモ (その3) - work sizeの調整
OpenCLメモ (その4) - 転送(コピー)の排除 (USE_HOST_PTR)
OpenCLメモ (その5) - 転送(コピー)の排除 (SVM : OpenCL 2.0)
OpenCLメモ (その6) - imageの使用
OpenCLメモ (その7) [終] - reductionとshared local memory(SLM)の使用