我在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线程并使应用程序无响应)?如果这会影响时间或导致奇怪,那么设置这样的测试应用程序的正确方法是什么?
答案 0 :(得分:3)
我自己在RenderScript中实现了CNN,正如您所解释的那样,如果您将每个进程实现为不同的内核,它确实需要链接多个进程并为每个层调用forEach_*()
不同的时间。因此,我可以向您保证,返回的forEach调用并不能真正保证该过程已完成。从理论上讲,这只会调度内核,所有排队的请求都会在系统确定最佳状态时实际运行,特别是如果它们在平板电脑的GPU中得到处理。
通常,绝对确保对真正运行的内核有某种控制的唯一方法是在层之间显式读取RS内核的输出,例如在输出分配上使用.copyTo()
该内核的对象。这会“强制”任何尚未运行的排队的RS作业(该层的输出分配依赖于该作业)在此时执行。当然,这可能会引入数据传输开销,并且您的时序不会完全准确 - 事实上,如果以这种方式计时,整个网络的执行时间肯定会低于各个层的总和。但据我所知,这是在链中计算单个内核的唯一可靠方法,它会为您提供一些反馈,以找出瓶颈所在,并更好地指导您的优化,如果这就是您所追求的。
答案 1 :(得分:3)
可能有点偏离主题:但是对于CNN,如果你可以使用矩阵 - 矩阵乘法作为基本计算块来构建算法,你实际上可以使用RenderScript IntrinsicBLAS,尤其是BNNM和{{3} }。
优点:
缺点:
如果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);