繁体   English   中英

如何在Nvidia Shield上正确计算Android RenderScript代码的时间

[英]How to do correct timing of Android RenderScript code on Nvidia Shield

我在RenderScript中实现了一个小型CNN,并希望在不同硬件上分析性能。 在我的Nexus 7上,时间是有意义的,但在NVIDIA Shield上他们没有。

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

时间的分布对于nexus来说是正确的,其中conv1和conv2(卷积层)占用大部分时间。 但是在盾牌上,时间下降的方式超出了2-4层的合理范围,并且似乎朝着最后的方向聚集起来。 softmax层是一个相对较小的工作,因此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 Studio将应用程序构建为版本,并在通过USB连接的设备上运行。

(1)为RenderScript进程计时的正确方法是什么? (2)当forEach_()返回时,脚本产生的线程是否可以保证完成? (3)在我的测试应用程序中,我只是直接从MainActivity运行。 这是一个问题(除了阻止UI线程并使应用程序无响应)? 如果这会影响时间或导致奇怪,那么设置这样的测试应用程序的正确方法是什么?

我自己用RenderScript实现了CNN,正如你所解释的那样,如果你把它们作为一个不同的内核实现,它确实需要链接多个进程并为每个层调用forEach_*()不同的时间。 因此,我可以向您保证,返回的forEach调用并不能真正保证该过程已完成。 从理论上讲,这只会调度内核,并且只要系统确定最佳请求,所有排队请求都会实际运行,特别是如果它们在平板电脑的GPU中处理完毕。

通常,绝对确保对真正运行的内核有某种控制的唯一方法是在层之间显式读取RS内核的输出,例如在该内核的输出分配对象上使用.copyTo() 。 。 这会“强制”任何尚未运行的排队的RS作业(该层的输出分配依赖于该作业)在此时执行。 当然,这可能会引入数据传输开销,并且您的时序不会完全准确 - 事实上,如果以这种方式计时,整个网络的执行时间肯定会低于各个层的总和。 但据我所知,这是对链中各个内核计时的唯一可靠方法,它会为您提供一些反馈,以找出瓶颈所在,并更好地指导您的优化,如果这就是您所追求的。

也许有点偏离主题:但是对于CNN,如果你可以使用矩阵 - 矩阵乘法作为基本计算块来构建算法,你实际上可以使用RenderScript IntrinsicBLAS ,尤其是BNNMSGEMM

优点:

  1. 8位矩阵乘法(BNNM)的高性能实现,可在N预览版中使用
  2. 当使用Build-Tools 24.0.0 rc3及更高版本时,通过RenderScript支持库返回支持Android 2.3。
  3. Nexus5X和6P上的SGEMM的高性能GPU加速与N预览构建NPC91K。
  4. 如果您只使用RenderScript内在函数,则可以使用java编写所有代码。

缺点:

  1. 您的算法可能需要重构,并且需要基于2d矩阵乘法。
  2. 虽然在Android 6.0中可用,但BNNM在6.0中的表现并不令人满意。 因此,最好为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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM