Posts for the month of January 2012

Tahiti ISAコードの最適化

AMDのGPU用のデバイスドライバ、Catalyst 12.1が公開された。4x4 DGEMMカーネルの性能がどれくらい変わったかをベンチしてみた。12.1とは「CAL 1.4.1664 (VM)」。

http://galaxy.u-aizu.ac.jp/permanent/DGEMM_12.1.png

最大性能は11.12では605 GFLOPSだったのが、12.1では675 GFLOPSまで上昇した。一割以上向上したことになる。生成されたコードは微妙に違う。入力のILコードはどちらも同じ。

11.12 https://gist.github.com/1632529

12.1 https://gist.github.com/1700860

メインループの部分、12.1では一度に全部のデータをロードしているのに対して、11.12では2回に分けてロードしている。ILの記述は11.12に近い。12.1はロードをまとめるような最適化をするようだ。

いずれの場合にも、メインループでネックになっていのるのは、小行列の行や列をロードするアドレス生成の部分。ここが長くなる理由は、今の行列格納形式がリニアなためで、この場合、小行列のロードは行列のサイズ分のstrideアクセスになる。一回のロードでは倍精度変数2語しかロードできないため、4語ロードのためには、アドレスが2つ必要であり、strideアクセス分のアドレス生成が必要になる。今のコードは1回のループアンローリングをしているので、アドレスは4個必要である。これをブロック化された格納形式にすると、この部分がリニアメモリの読み出しになり、ロード自体が高速になるはずで、かつアドレス計算も単純なインクリメントですむ。というような最適化はいずれ。

参考までに、SGEMMの性能は以下のような感じ。8x8のブロッキング。こちらはピーク性能比でかなり低いのでまだ最適化の余地が大きい。色々とテスト中。

http://galaxy.u-aizu.ac.jp/permanent/SGEMM_12.1.png

コメントはこちらへ:https://plus.google.com/117333869988556483969/posts/BpWbwwzggXR

南の島のISA

先日発売されたAMD Radeon 7970の命令セット(ISA)について、実際のコードをみながらコメントしてみよう。例としてILでpixel shaderとして記述されたDGEMMカーネルから生成されたコードをgistに貼りつけた。利用したSDKのバージョンは"OpenCL 1.1 AMD-APP (831.4)"。

https://gist.github.com/1632529

前説

まず、前提として、このカーネルでは4x4のレジスタブロッキングをしている。そのため、ループの中ではそれぞれ4ワードを行列A, Bから読み込み、外積形式出部分行列の積を計算し、和を保持する4x4 = 16ワードの部分行列Cをアップデートする構造となっている。詳しくは、

http://galaxy.u-aizu.ac.jp/note/wiki/Fast_GEMM_Implementation_On_Cypress

にあるスライド参照。このカーネルはスライドの説明にあるところのTNカーネルに対応する。

また我々の最新のDGEMMカーネルのパフォーマンスについては、

Multi-level Optimization of Matrix Multiplication for GPU-equipped Systems, K.Matsumoto etal. http://dx.doi.org/10.1016/j.procs.2011.04.036

を参照のこと。この論文では巨大行列のためのDGEMMの性能最適化について報告している。

概観

コードをみてすぐに分かるのは"v_"と"s_"のprefixがついた命令があることである。これはprefixから予想されるように、ベクトル命令とスカラー命令なのだろう。これらの命令は、パッと見たところ完全に混在している。レジスタは"vn"の形式で32bitごとにアクセスできることがわかる。また"v[n:n+1]"の形式は二つの連続する32bitレジスタを表すのだろうから、これは64bitの1ワードに相当する。このカーネルの場合これは倍精度変数である。

コード右側の""というコメント(と思われる)以降にある数字の意味は不明。普通に考えるとbinary形式だろうけど。

個々の命令については現時点ではdocumentが公開されていないので、名前から動作を推測するしかない。とはいえ、一部を除いて、どの命令も明示的な名前が付けられており、特記するまでもない。"image_sample"命令は、texture unit経由のデータ読み込みを意味すると思われる。

レビュー

コードの流れは単純で、最初に部分行列Cを初期化して(20 - 51行:32bitで32ワードを0で初期化)、52行のラベルからループに突入し、123行の"s_branch"命令がループの終了部分。それ以降は"alpha C + beta AB"を計算している。この最後の部分(125 - 252行)が妙に長くなっているが、カーネルあたり一度しか実行されないので、計算時間上のインパクトはない。実際、上記K.Matsumoto論文によると、GEMMのカーネルとしては、このカーネルのように"C <- alpha C + beta AB"の全てを計算するのは高速ではなく、ABを計算するカーネルを利用し、Cとの和はホストで計算したほうが高速である。ここではあくまで一番シンプルな実装を紹介しているにすぎない。いずれにしろ、この部分では部分行列Cをロードして、レジスタに保持されているカーネルで計算した結果との和をとる処理をするため、"tbuffer_load"や"tbuffer_store"という命令が使われている。

ということでDGEMMカーネルとして計算時間がかかるループの本体部分は52 - 123行である。このループの大部分は"v_fma_f64"という命令が占めている。これはその名の通りの倍精度FMA命令だろう。FMA命令はは素直に4 operandsの命令になっている(古い世代のCypressやCaymanでもFMA命令は4 operandsだった)。なお、AMD社は最新のx86_64のCPUでも4 operandsのFMA命令を採用している。一方Intel社は、将来的には3 operandsのFMA命令を採用することになっている。両社の方針の違いは、将来的にはx86_64 CPUに統合されるであろうGPUのアーキテクチャにも反映されている/深く関係しているのかもしれない。

53 - 60行はループの終了条件を判定している部分になる。"s_waitcnt"はよくわからない。こここでthreadが切り替わっているのかもしれない。

その意味では61行目のラベルからがループ本体であり、62 - 67行はデータをロードするアドレスの準備をしているようにみえる。この部分は冗長にみえる。

69 - 72行で行列Aと行列Bをロードしている。"image_sample"では、一番左のレジスタがdestinationだろうから、ひとつの命令あたり32bitで4ワードをロードしている。よって、この4命令で合計倍精度変数を8ワード、ロードすることになる。

74 - 90行で、ロードした変数どうしの外積形式による行列乗算がおこなわれる。"v_fma_f64"の個数は16個であり、これは4x4のレジスタブロッキングをしているので当然。ところどころwait用?の命令が挟み込まれている。

91 - 100行でまたなんらかのアドレスの準備をして、101 - 104行で再び行列AとBをロードする。実はこのカーネルでは2段のループアンローリングをしているのでこうなっている。実際74 - 90行と処理自体は同等だ。

不可解なのはループを抜けたあとの126 - 147行で、なぜかレジスタ同士で入れ替えをしている。入れ替えをする理由は、その後で結果をメモリに書き込むためで、その時にレジスタ上で連続するようにという理由付けはできるが、実際にはv15 - v36に連続しているものをv12 - v33にコピーしているので、よくわからない。

追加コメント

全体を見ると、これくらいであれば手で書こうと思えば書けなくもないかもしれない。ただ、アドレス計算はやっぱり面倒。

ループ本体の中にデータロードとFMA以外の命令が43%も含まれている。このコードの性能は最大600 GFLOPSであるが、これはピーク性能の63%に相当する。これは偶然なのかどうか、というところが、今後の最適化の指針になるだろう。

126 - 147行は明らかに不必要。

随時アップデートするかもー。

GitLabをreverse proxyで動作させる

http://gitlabhq.com/ のインストールは罠が多い。失敗を何度か繰り返した後に、結局この https://github.com/gitlabhq/gitlabhq/wiki/V2.0-easy-setup-for-ubuntu とおりに一字一句やればよい。Ubuntu 10.04 LTSで確認。この文書には書いていないがzlib1g-devをインストールしておかないと、ruby1.9がzlibなしになってしまい、うまく動かない。ちなみに"git"と"gitlabhq"というuserを追加する。他のドキュメントはここを省略しているので、"git"というuserのみでやろうとするとハマる。

次にwebrickで動作しているこのGitLabアプリをapacheを通して外から見えるようにしたい。ここでまた色々な罠が。最終的には、現バージョンのRails3(3.1.3)でURLにprefixをつけるには

http://stackoverflow.com/a/7367886

が正解。config/routes.rbに

Gitlab::Application.routes.draw do
scope "/git" do
...略...
end
end

"scope"と"end"の二行を、内側に追加する。その上で、apacheのreverse proxyの設定は

ProxyPass        /git http://localhost:3000/git
ProxyPassReverse /git http://localhost:3000/git
ProxyPass        /assets http://localhost:3000/assets
ProxyPassReverse /assets http://localhost:3000/assets

とする必要がある(example.com/gitでアクセスする場合)。後者の設定は/asstes配下の画像ファイルやJSファイルのために必要。routes.rbを上記のように書き換えても、/assets配下のURLは。。。うーん。

magic fileがないみたいなエラーがでる場合

sudo gem install charlock_holmes

で、明示的に"charlock_holmes"すればよい。

南島アーキテクチャ

2011年12月22日にRadeon HD 7900(Tahiti)について発表があった。アーキテクチャの概要については、日本語ではPC Watchの以下の記事にまとまっている。

http://pc.watch.impress.co.jp/docs/column/kaigai/20111222_501138.html

GPUのアーキテクチャ名はSouthern Islandsとなる。ちなみに前世代(Radeon HD 6900: Cayman)はNorthern Islandsと呼ばれている。実際、AMDが12月に公開した文書AMD Accelerated Parallel Processing OpenCL™ Programming Guide (v1.3f) の4-79ページに、新旧アーキテクチャの比較がまとめられている。

プログラミングの観点からすると、大きな違いはVLIW4と呼ばれるベクトル長4の演算ユニットの塊から、スカラー演算ユニット(言い換えるとベクトル長1)の塊へなったこと。そのためOpenCL用語でCompute Unit(CU)と呼ばれるCPUにおける「コア」に相当するユニットのベクトル長は、北島では4x16=64であったのが、南島では16になった。以下に、上記の文書にある図をコピーして貼り付ける。

http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/OpenCL_Memo/NI.png http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/OpenCL_Memo/SI.png

北島の図はVLIW4のユニットであり、北島のCUはこれが16個まとまったものになる(そのためこの図は正確ではないことに注意)。南島の図はCU全体である。どこかで見かけたAMDの人のプレゼンテーション(検索中)によると、演算ユニット自体はVLIW4を流用しているとのこと。そのため、南島はベクトル長16のはずなので、図の「VECTOR ALU」はベクトル長4のはずであり、この部分は北島の図の「xyzw」演算器の部分を流用しているようだ。これらの図における演算器の最小単位は32 bit演算器である。VLIW4を流用していることから、浮動小数点単精度演算の性能と倍精度演算の性能比は、新旧で変わらず加算では2:1で、乗算またはFMAでは4:1となる。実際には、これに詳細不明のスカラー演算器の性能分が加わるが、ベクトル演算器とスカラー演算器の個数比は16:1のため、ピーク性能へのインパクトは大きくない。

もう一つ、この文書にはメモリ要素を比べたテーブルがある。

http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/OpenCL_Memo/GPU_REGFILE.png

32 bit演算器あたりのレジスタ数は新旧で同じ256ワードであるが、北島はベクトル長4のベクトルレジスタであるのに対し、南島はレジスタもスカラーであるから、完璧にVLIWでベクトル化されているコードを除いて、レジスタの利用効率は南島のほうがよいはずである。それと、CUあたりの1次キャッシュが倍増し、CUあたりのLDS量も倍増して、さらにメモリチャンネルが4から6になったことで、2次キャッシュも増えている。キャッシュは北島では読み出し専用だったのが、読み書き可能になったとのこと。

Tahitiのリファレンス動作周波数は925MHzであり、この時の演算性能は単精度FMAで3788 GFLOPSになる。一方で、海外サイトのソース不明記事によると、オーバークロックされたGPUボードがすでに準備されているらしく、その動作周波数は1.3GHzに達するようだ。この場合の性能は約5.3/1.3 TFLOPS(単精度/倍精度FMA)になる。TSMCの28nmプロセスを待ったかいは非常にあるのかもしれない。