Delegado NNAPI de TensorFlow Lite

La API de redes neuronales de Android (NNAPI) está disponible en todos los dispositivos Android que ejecutan Android 8.1 (nivel de API 27) o superior. Proporciona aceleración para modelos TensorFlow Lite en dispositivos Android con aceleradores de hardware compatibles, que incluyen:

  • Unidad de procesamiento de gráficos (GPU)
  • Procesador de señal digital (DSP)
  • Unidad de procesamiento neuronal (NPU)

El rendimiento variará según el hardware específico disponible en el dispositivo.

Esta página describe cómo utilizar el delegado NNAPI con el intérprete de TensorFlow Lite en Java y Kotlin. Para las API de Android C, consulte la documentación del kit para desarrolladores nativos de Android .

Probar el delegado NNAPI en su propio modelo

Importación de Gradle

El delegado de NNAPI es parte del intérprete de Android de TensorFlow Lite, versión 1.14.0 o superior. Puede importarlo a su proyecto agregando lo siguiente al archivo gradle de su módulo:

dependencies {
   implementation 'org.tensorflow:tensorflow-lite:+'
}

Inicializando el delegado NNAPI

Agregue el código para inicializar el delegado NNAPI antes de inicializar el intérprete de TensorFlow Lite.

kotlin

import android.content.res.AssetManager
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.nnapi.NnApiDelegate
import java.io.FileInputStream
import java.io.IOException
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
...

val options = Interpreter.Options()
var nnApiDelegate: NnApiDelegate? = null
// Initialize interpreter with NNAPI delegate for Android Pie or above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    nnApiDelegate = NnApiDelegate()
    options.addDelegate(nnApiDelegate)
}
val assetManager = assets

// Initialize TFLite interpreter
val tfLite: Interpreter
try {
    tfLite = Interpreter(loadModelFile(assetManager, "model.tflite"), options)
} catch (e: Exception) {
    throw RuntimeException(e)
}

// Run inference
// ...

// Unload delegate
tfLite.close()
nnApiDelegate?.close()

...

@Throws(IOException::class)
private fun loadModelFile(assetManager: AssetManager, modelFilename: String): MappedByteBuffer {
    val fileDescriptor = assetManager.openFd(modelFilename)
    val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
    val fileChannel = inputStream.channel
    val startOffset = fileDescriptor.startOffset
    val declaredLength = fileDescriptor.declaredLength
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
}

...

Java

import android.content.res.AssetManager;
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.nnapi.NnApiDelegate;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
...

Interpreter.Options options = (new Interpreter.Options());
NnApiDelegate nnApiDelegate = null;
// Initialize interpreter with NNAPI delegate for Android Pie or above
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    nnApiDelegate = new NnApiDelegate();
    options.addDelegate(nnApiDelegate);
}

AssetManager assetManager = getAssets();
// Initialize TFLite interpreter
try {
    tfLite = new Interpreter(loadModelFile(assetManager, "model.tflite"), options);
} catch (Exception e) {
    throw new RuntimeException(e);
}

// Run inference
// ...

// Unload delegate
tfLite.close();
if(null != nnApiDelegate) {
    nnApiDelegate.close();
}

...

private MappedByteBuffer loadModelFile(AssetManager assetManager, String modelFilename) throws IOException {
    AssetFileDescriptor fileDescriptor = assetManager.openFd(modelFilename);
    FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());
    FileChannel fileChannel = inputStream.getChannel();
    long startOffset = fileDescriptor.getStartOffset();
    long declaredLength = fileDescriptor.getDeclaredLength();
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
}

...

Mejores prácticas

Pruebe el rendimiento antes de implementar

El rendimiento en tiempo de ejecución puede variar significativamente debido a la arquitectura del modelo, el tamaño, las operaciones, la disponibilidad del hardware y la utilización del hardware en tiempo de ejecución. Por ejemplo, si una aplicación utiliza mucho la GPU para renderizar, es posible que la aceleración NNAPI no mejore el rendimiento debido a la contención de recursos. Recomendamos ejecutar una prueba de rendimiento simple utilizando el registrador de depuración para medir el tiempo de inferencia. Ejecute la prueba en varios teléfonos con diferentes conjuntos de chips (fabricante o modelos del mismo fabricante) que sean representativos de su base de usuarios antes de habilitar NNAPI en producción.

Para desarrolladores avanzados, TensorFlow Lite también ofrece una herramienta de referencia de modelos para Android .

Crear una lista de exclusión de dispositivos

En producción, puede haber casos en los que NNAPI no funcione como se esperaba. Recomendamos a los desarrolladores mantener una lista de dispositivos que no deberían usar la aceleración NNAPI en combinación con modelos particulares. Puede crear esta lista basándose en el valor de "ro.board.platform" , que puede recuperar utilizando el siguiente fragmento de código:

String boardPlatform = "";

try {
    Process sysProcess =
        new ProcessBuilder("/system/bin/getprop", "ro.board.platform").
        redirectErrorStream(true).start();

    BufferedReader reader = new BufferedReader
        (new InputStreamReader(sysProcess.getInputStream()));
    String currentLine = null;

    while ((currentLine=reader.readLine()) != null){
        boardPlatform = line;
    }
    sysProcess.destroy();
} catch (IOException e) {}

Log.d("Board Platform", boardPlatform);

Para desarrolladores avanzados, considere mantener esta lista a través de un sistema de configuración remota. El equipo de TensorFlow está trabajando activamente en formas de simplificar y automatizar el descubrimiento y la aplicación de la configuración NNAPI óptima.

Cuantización

La cuantificación reduce el tamaño del modelo mediante el uso de números enteros de 8 bits o flotantes de 16 bits en lugar de flotantes de 32 bits para el cálculo. Los tamaños de los modelos enteros de 8 bits son una cuarta parte de los de las versiones flotantes de 32 bits; Los flotantes de 16 bits tienen la mitad del tamaño. La cuantificación puede mejorar significativamente el rendimiento, aunque el proceso podría compensar cierta precisión del modelo.

Hay varios tipos de técnicas de cuantificación posteriores al entrenamiento disponibles, pero, para obtener el máximo soporte y aceleración en el hardware actual, recomendamos la cuantificación de números enteros completos . Este enfoque convierte tanto el peso como las operaciones en números enteros. Este proceso de cuantificación requiere un conjunto de datos representativo para funcionar.

Utilice modelos y operaciones compatibles

Si el delegado de NNAPI no admite algunas de las operaciones o combinaciones de parámetros en un modelo, el marco solo ejecuta las partes admitidas del gráfico en el acelerador. El resto se ejecuta en la CPU, lo que da como resultado una ejecución dividida. Debido al alto costo de la sincronización de CPU/acelerador, esto puede resultar en un rendimiento más lento que ejecutar toda la red solo en la CPU.

NNAPI funciona mejor cuando los modelos solo usan operaciones compatibles . Se sabe que los siguientes modelos son compatibles con NNAPI:

La aceleración NNAPI tampoco se admite cuando el modelo contiene salidas de tamaño dinámico. En este caso, recibirá una advertencia como:

ERROR: Attempting to use a delegate that only supports static-sized tensors \
with a graph that has dynamic-sized tensors.

Habilitar la implementación de CPU NNAPI

Un gráfico que un acelerador no puede procesar completamente puede recurrir a la implementación de CPU NNAPI. Sin embargo, dado que este suele tener menos rendimiento que el intérprete de TensorFlow, esta opción está deshabilitada de forma predeterminada en el delegado NNAPI para Android 10 (API nivel 29) o superior. Para anular este comportamiento, establezca setUseNnapiCpu en true en el objeto NnApiDelegate.Options .