Cómo sincronizar correctamente el código RenderScript de Android en Nvidia Shield

He implementado un pequeño CNN en RenderScript y quiero perfilar el rendimiento en diferentes hardware. En mi Nexus 7 los tiempos tienen sentido, pero en el escudo de NVIDIA no lo hacen.

La CNN (LeNet) se implementa en 9 capas que residen en una cola, el cómputo se realiza en secuencia. Cada capa se cronometra individualmente.

Aquí hay un ejemplo:

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 

La distribución de los tiempos es aproximadamente correcta para el nexo, con conv1 y conv2 (capas de convolución) tomando la mayor parte del tiempo. Pero en el escudo, los tiempos caen mucho más allá de lo que es razonable para las capas 2-4 y parecen reunirse hacia el final. La capa softmax es un trabajo relativamente pequeño, por lo que 40ms es demasiado grande. Mi método de cronometraje debe ser defectuoso, o algo está sucediendo.

El código que ejecuta las capas se ve así:

 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++; } 

Es mi entendimiento que una vez que forEach_ () devuelve, el trabajo se supone que está terminado. En cualquier caso, mRS.finish () debería proporcionar una barrera final. Pero mirando los tiempos, la única explicación razonable es que los trabajos todavía se procesan en el fondo.

La aplicación es muy simple, sólo ejecutar la prueba de MainActivity e imprimir a logcat. Android Studio genera la aplicación como una versión y la ejecuta en el dispositivo que está conectado mediante USB.

(1) ¿Cuál es la forma correcta de procesar tiempo RenderScript? (2) ¿Es cierto que cuando forEach_ () devuelve, los hilos generados por el script están garantizados para ser hecho? (3) En mi aplicación de prueba, simplemente ejecuto directamente desde MainActivity. ¿Es esto un problema (aparte de bloquear el hilo de la interfaz de usuario y hacer que la aplicación no responda)? Si esto influye en el momento o causa la rareza, ¿cuál es una forma adecuada de configurar una aplicación de prueba como ésta?

He implementado CNNs en RenderScript yo mismo, y como usted explica, requiere encadenar múltiples procesos y llamar a forEach_*() varias veces para cada capa si los implementa cada uno como un kernel diferente. Como tal, puedo asegurarle que el forEach llamada que vuelve no garantiza realmente que el proceso ha terminado. En teoría, esto sólo programará el kernel y todas las solicitudes en cola se ejecutarán cuando el sistema determine que es mejor, especialmente si se procesan en la GPU de la tableta.

Por lo general, la única manera de asegurarse de que tiene algún tipo de control sobre un kernel realmente ejecutado es leyendo explícitamente la salida del kernel RS entre capas, como por ejemplo .copyTo() en el objeto de asignación de salida de ese kernel . Esto "obliga" a cualquier trabajo de RS en cola que aún no se ha ejecutado (en el que depende la asignación de salida de esa capa), para ejecutarse en ese momento. Por supuesto, que puede introducir gastos de transferencia de datos y su calendario no será totalmente precisa – de hecho, el tiempo de ejecución de la red completa seguramente será menor que la suma de las capas individuales si cronometrada de esta manera. Pero por lo que sé, es la única forma fiable de tiempo kernels individuales en una cadena y le dará algunos comentarios para saber dónde están los cuellos de botella, y para orientar mejor su optimización, si eso es lo que buscas.

Tal vez un poco fuera de tema: pero para CNN, si se puede estructurar el algoritmo utilizando matriz-matriz de multiplicación como bloques básicos de cálculo que puede utilizar RenderScript IntrinsicBLAS , especialmente BNNM y SGEMM .

Pros:

  1. Implementación de alto rendimiento de 8bit Matrix Multiplication (BNNM), disponible en N Preview .
  2. Volver apoyo de nuevo a Android 2.3 a través de RenderScript lib de soporte , cuando se utiliza Build-Tools 24.0.0 rc3 y superior.
  3. Aceleración de GPU de alto rendimiento de SGEMM en Nexus5X y 6P con N Previsualizar construir NPC91K.
  4. Si sólo utiliza RenderScript Intrinsics, puede codificar todo en java.

Contras:

  1. Su algoritmo puede necesitar ser refactorizado, y debe basarse en la multiplicación de matriz 2D.
  2. Aunque disponible en Android 6.0, pero el rendimiento de BNNM en 6.0 no es satisfactorio. Por lo tanto, es mejor utilizar lib de soporte para BNNM y establecer targetSdkVersion como 24.
  3. La aceleración de la GPU SGEMM actualmente sólo está disponible en Nexus5X y Nexus6P. Y actualmente requiere que el ancho y la altura de las Matrices sean múltiplos de 8.

Vale la pena intentar si BLAS se ajusta a su algoritmo. Y es fácil de usar:

  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 es similar:

  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); 
  • ¿Cómo rastreo la "pantalla en" el tiempo en Android?
  • Retardo de tiempo en Android
  • Java cómo incrementar un int 1 cada segundo hasta que llegue a 15
  • ¿Cómo puedo obtener la fecha y la hora del proveedor de la red?
  • Android limitar el tiempo de grabación con la intención
  • ¿Cómo puedo mostrar la hora en formato 24 horas usando un TextClock?
  • Convertir cadena al formato de sello de tiempo de MySQL en php
  • SimpleDateFormat.getTimeInstance ignora el formato de 24 horas
  • Tiempo de sincronización para la grabación de datos en varios dispositivos Android
  • Tiempo del GPS en Android
  • Java cronometrar, System.nanoTime () batear que System.currentTimeMillis () pero persiste sobre sueño?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.