今の状況を書いておくと、
https://github.com/tanakamura/waifu2x-converter-cpp/blob/gpu/src/modelHandler_OpenCL.cl
いちおう動いている、が、FMA版よりまだ遅い。i7 4700MQ vs GTX 765M でFLOPS値 200 : 80 ぐらい 765M は 1300GFLOPS なので効率6%ぐらい。
FMA 版と同じように、outputplane を最内ループにすると、係数全部ローカルメモリに乗せないといけないのだが、16KBでは全然入らない。(最大576KBになる)
案としては、
- occupancy に頼らない。参考:http://www.cs.berkeley.edu/~volkov/volkov10-GTC.pdf
- SMあたり65536,32768 レジスタあることを考えると、128x9 は余裕で全部レジスタにのる。ただ、これを分けるとxxxxxが必要になる。まあ、スレッドあたり63レジスタまで使えると考えると、4x9ぐらいは1スレッドに乗るので、それなら32thread reductionならまあ許容範囲?
- for (x) { for (inputPl) { for (outputPl){} } }
- 128x128x9 = 147456 = 3SM あればレジスタに乗る。outputPlane を 65535(強いNVIDIA)レジスタあるマシンでは3つか4つ、32768(弱いNVIDIA)マシンでは8ぐらい、16384(AMD)では16ぐらいに分ける
- for (x // lmemに入るだけブロッキング) { for (outputPlBlock) { for(inputPl) { for (outputPl) { } } } } // 没
// 没 for (x=0; x<width; x+=X_BLOCK_SIZE) { for (ip=0; ip<inputPlane; ip++) { coef0 = coef@global[0*32 + lid]; coef.. = coef@global[.. + lid]; coef8 = coef@global[8*32 + lid]; x_block@local[] = input@global(ip,x,y) if (ip != 0) { sum = sum@global(op, } for (xb=0; xb<X_BLOCK_SIZE; xb++) { } } }
- for (x) { for (inputPlane) { for (output) { for (x_block // 64@NV, ) { for (outputblock // 4) {} } sum@global[64*output] = v } } } こうかな
- 1. inputPlane は局所性いちばんきくので外側
- 2. 没
- outputPlane を最内にすると中間データが増える。入力の局所性が上がる
- xi を最内にすると気合いだけで演算メモリ比は9:2あたりまで持っていける、が、そこに限界がある
- inputPlane を最内にすると中間データは要らないが、入力の局所性が無くなる
つまりまとめると
- outputPlane 方向にN回ブロッキングすると、入力メモリ量1/N になる。中間メモリ使用量がN倍になる。出力メモリ回数は変わらない
- inputPlane 方向にM回ブロッキングすると、出力メモリ量が1/Mになる。inputを保持するローカルメモリがM倍になる。入力メモリ回数は変わらない
- 使用レジスタ数は N x M に比例(weight を全部レジスタに乗せるなら)
というトレードオフか。
これを踏まえると、手順としては、
- xi を最内にして、演算メモリ比 9:2 版を作る (多分効率5%ぐらい)
- outputPlane 方向に4回ぐらいブロッキング。メモリネックなら2倍に近づくはず(効率10%、レジスタ36個)
- occupancy が下がらない程度のローカルメモリ量になるまで inputPlaneを増やしていく (余計な命令が増えて演算ネックになってくるはず。効率15%)
という手順かな。
いやそれでも15%か。30%ぐらいはいかないといけないのだけど。