2016-05-06 5 views
6

RenderScriptで小さなCNNを実装し、別のハードウェアでパフォーマンスをプロファイルしたいと思っています。私のNexus 7では時が意味をなさないが、NVIDIA Shieldではそうではありません。Nvidia ShieldでAndroid RenderScriptコードの正しいタイミングを行う方法

CNN(LeNet)は、キューに常駐する9つのレイヤーで実装され、計算が順番に実行されます。各レイヤーは個別にタイミングが取られます。ここ

は一例であり:

 conv1 pool1 conv2 pool2 resh1 ip1 relu1 ip2 softmax 
nexus7 11.177 7.813 13.357 8.367 8.097 2.1 0.326 1.557 2.667 
shield 13.219 1.024 1.567 1.081 0.988 14.588 13.323 14.318 40.347 

時間の分布はCONV1とCONV2(畳み込み層)がほとんどの時間を取ると、ネクサスのための右の程度です。しかし、盾の上では、時間はレイヤ2〜4のために妥当なものを超えて下がり、最後に向かって集まるように見えます。ソフトマックスレイヤーは比較的小さいジョブなので、40msは大きすぎます。私のタイミング方法は間違っていなければならない、または何か他のことが起こっている。

double[] times = new double[layers.size()]; 
int layerindex = 0; 
for (Layer a : layers) { 

    double t = SystemClock.elapsedRealtime(); 
    //long t = System.currentTimeMillis(); // makes no difference 

    blob = a.forward(blob); // here we call renderscript forEach_(), invoke_() etc 

    //mRS.finish(); // makes no difference 

    t = SystemClock.elapsedRealtime() - t; 
    //t = System.currentTimeMillis() - t; // makes no difference 

    times[layerindex] += t; // later we take average etc 

    layerindex++; 
} 

それは一度forEach_()リターンが、ジョブが終了することになっている私の理解です:レイヤーを実行している

のコードは次のようになります。いずれにしても、mRS.finish()は最終的な障壁となります。しかし、時代を見れば、唯一の妥当な説明は、仕事はまだバックグラウンドで処理されているということです。

アプリはとてもシンプルですが、私はMainActivityからテストを実行してlogcatに出力します。 Androidスタジオは、アプリをリリースとして構築し、USBで接続されたデバイス上で実行します。

(1)RenderScriptプロセスを実行する正しい方法は何ですか? (2)forEach_()が返ってくると、スクリプトによって生成されたスレッドが確実に実行されることは保証されていますか? (3)私のテストアプリケーションでは、単にMainActivityから直接実行します。これは問題ですか(UIスレッドをブロックしてアプリを応答しないようにする以外)?これがタイミングに影響を与えたり、奇妙なことが起こった場合、このようなテストアプリケーションを設定する適切な方法は何ですか?

+0

CNNの略? –

+0

畳み込みニューラルネットワーク。 – frankhond

答えて

3

RenderScriptでCNNを実装しました。説明したように、複数のプロセスを連鎖させ、別のカーネルとして実装する場合は、各レイヤーに対してさまざまな時間を呼び出す必要があります(forEach_*())。そのため、それぞれの呼び出しが返されても、プロセスが完了したことが実際に保証されるわけではありません。理論的には、これはカーネルのスケジューリングのみを行い、キューに入れられたすべてのリクエストは、特にタブレットのGPUで処理される場合にシステムが最良と判断するたびに実際に実行されます。

通常、実際にカーネルを制御する何らかの制御を確実にする唯一の方法は、その出力割り当てオブジェクトで.copyTo()を使用するなど、レイヤ間でRSカーネルの出力を明示的に読み取ることですカーネル。これは、まだ実行されていないキューイングされたRSジョブ(そのレイヤの出力割り当てが依存する)をその時点で実行するよう強制します。データ転送のオーバーヘッドが発生する可能性があり、タイミングが完全に正確ではありません。実際、完全なネットワークの実行時間は、このように時間が設定されていれば個々のレイヤーの合計よりも確実に低くなります。しかし、私が知る限り、それはチェーン内の個々のカーネルの時間を決める唯一の信頼できる方法であり、ボトルネックがどこにあるかを知るためのフィードバックや、最適化の指針となります。

+0

ありがとう! RenderScriptの理解に貢献した他の質問を読んだことがあります。もう少しあなたの脳を選ぶことができますか?このようなものをShieldのようなGPUを搭載したタブレットでテストすると、コードがGPU上で実際に動作するかどうかを確認するにはどうすればよいでしょうか? – frankhond

+0

えーと、いつものように、RSカーネルがどこで実行されるかは分かりにくいですが、タイミング設定が固定されていれば、カーネルが正常に動作している状態でテストを実行し、RSを使用してCPUを使用する'adb shell setprop debug.rs.default-CPU-driver 1'(そしてそれをオフにするには0、それを有効にするためにこれを変更する間にアプリケーションを適切に終了させて​​ください)でのみ。あなたのカーネルがうまく構築されていて、実際にGPUを使用している場合、顕著な違いが見られるはずです。特に畳み込みでは、CPU専用モードに比べて少なくとも4〜6倍のスピードアップが必要です。 – monoeci

+0

ニース、私はそれを試してみよう!どこかで利用できるプロファイラがあることを期待していましたが、これは確かに目立つでしょう。あなたの助けをもう一度ありがとう! – frankhond

3

CNNの場合、基本的な計算ブロックとして行列 - 行列乗算を使用してアルゴリズムを構成できれば、実際にはBNNMSGEMMを使用できます。

長所:8ビット行列乗算(BNNM)の

  1. 高性能実装、N Previewで利用できます。
  2. Build-Tools 24.0.0 rc3以上を使用している場合は、Android 2.3からRenderScript Support libまでのサポートを元に戻します。
  3. Nexus5Xおよび6P(NプレビュービルドNPC91K)のSGEMMの高性能GPUアクセラレーション
  4. RenderScript Intrinsicsのみを使用する場合は、すべてをjavaでコーディングできます。

短所:

  1. あなたのアルゴリズムをリファクタリングする必要があり、2D行列の乗算に基づいてする必要があるかもしれません。
  2. Android 6.0でも利用できますが、6.0のBNNMのパフォーマンスは満足できるものではありません。したがって、BNNMのサポートlibを使用し、targetSdkVersionを24に設定する方が良いです。
  3. SGEMM GPU加速は現在、Nexus5XとNexus6Pでのみ利用可能です。現在、マトリックスの幅と高さは8の倍数にする必要があります。

BLASがアルゴリズムに適合するかどうか試してみる価値はあります。そして、使いやすいです:

import android.support.v8.renderscript.*; 
    // if you are not using support lib: 
    // import android.renderscript.*; 

    private void runBNNM(int m, int n, int k, byte[] a_byte, byte[] b_byte, int c_offset, RenderScript mRS) { 
     Allocation A, B, C; 
     Type.Builder builder = new Type.Builder(mRS, Element.U8(mRS)); 
     Type a_type = builder.setX(k).setY(m).create(); 
     Type b_type = builder.setX(k).setY(n).create(); 
     Type c_type = builder.setX(n).setY(m).create(); 

     // If you are reusing the input Allocations, just create and cache them somewhere else. 
     A = Allocation.createTyped(mRS, a_type); 
     B = Allocation.createTyped(mRS, b_type); 
     C = Allocation.createTyped(mRS, c_type); 
     A.copyFrom(a_byte); 
     B.copyFrom(b_byte); 

     ScriptIntrinsicBLAS blas = ScriptIntrinsicBLAS.create(mRS); 
     // Computes: C = A * B.Transpose 
     int a_offset = 0; 
     int b_offset = 0; 
     int c_offset = 0; 
     int c_multiplier = 1; 
     blas.BNNM(A, a_offset, B, b_offset, C, c_offset, c_multiplier); 
    } 

SGEMMが似ています。

 ScriptIntrinsicBLAS blas = ScriptIntrinsicBLAS.create(mRS); 
     // Construct the Allocations: A, B, C somewhere and make sure the dimensions match. 
     // Computes: C = 1.0f * A * B + 0.0f * C 
     float alpha = 1.0f; 
     float beta = 0.0f; 
     blas.SGEMM(ScriptIntrinsicBLAS.NO_TRANSPOSE, ScriptIntrinsicBLAS.NO_TRANSPOSE, 
        alpha, A, B, beta, C); 
+0

例をありがとう。私はすでに、im2colとSGEMMとの行列乗算と動くカーネルバージョンを使って畳み込みを実装しました。私の質問の理由は、私はこれらの2つのアルゴリズムをもう一度お互いにプロファイルしようとしているということです。ハードウェアによっては、私は大きく異なる結果を得て、なぜそれを理解しようとします。 BNNMの例は、私が次のステップに移っていく中でとても役に立ちます。 – frankhond

+0

また、GPUの情報をありがとう、非常に便利です。これはどこかでオンラインで見つけることができますか? – frankhond

+0

まだありません。しかし、それについてのチュートリアルとドキュメンテーションがたくさんあります。私はそれが利用可能になったら、ここでそれを更新しておきます。また、RenderScriptに関する提案や機能要求がある場合は、私にお知らせください。私はそれをRenderScriptチームに転送することができます。ありがとう! –

関連する問題