Delegado NNAPI do TensorFlow Lite

A Android Neural Networks API (NNAPI) está disponível em todos os dispositivos Android que executam o Android 8.1 (API de nível 27) ou superior. Ele fornece aceleração para modelos TensorFlow Lite em dispositivos Android com aceleradores de hardware compatíveis, incluindo:

  • Unidade de processamento gráfico (GPU)
  • Processador de Sinal Digital (DSP)
  • Unidade de Processamento Neural (NPU)

O desempenho irá variar dependendo do hardware específico disponível no dispositivo.

Esta página descreve como usar o delegado NNAPI com o interpretador TensorFlow Lite em Java e Kotlin. Para APIs Android C, consulte a documentação do Android Native Developer Kit .

Experimentando o delegado NNAPI em seu próprio modelo

Importação Gradle

O delegado NNAPI faz parte do interpretador TensorFlow Lite Android, versão 1.14.0 ou superior. Você pode importá-lo para o seu projeto adicionando o seguinte ao arquivo gradle do módulo:

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

Inicializando o delegado NNAPI

Adicione o código para inicializar o delegado NNAPI antes de inicializar o interpretador do 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);
}

...

Melhores Práticas

Teste o desempenho antes de implantar

O desempenho do tempo de execução pode variar significativamente devido à arquitetura do modelo, tamanho, operações, disponibilidade de hardware e utilização do hardware em tempo de execução. Por exemplo, se um aplicativo utiliza intensamente a GPU para renderização, a aceleração NNAPI pode não melhorar o desempenho devido à contenção de recursos. Recomendamos executar um teste de desempenho simples usando o registrador de depuração para medir o tempo de inferência. Execute o teste em vários telefones com chipsets diferentes (fabricante ou modelos do mesmo fabricante) que sejam representativos de sua base de usuários antes de ativar o NNAPI em produção.

Para desenvolvedores avançados, o TensorFlow Lite também oferece uma ferramenta de benchmark de modelo para Android .

Crie uma lista de exclusão de dispositivos

Na produção, pode haver casos em que o NNAPI não tenha o desempenho esperado. Recomendamos que os desenvolvedores mantenham uma lista de dispositivos que não devem usar aceleração NNAPI em combinação com modelos específicos. Você pode criar essa lista com base no valor de "ro.board.platform" , que pode ser recuperado usando o seguinte trecho 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 desenvolvedores avançados, considere manter esta lista por meio de um sistema de configuração remota. A equipe do TensorFlow está trabalhando ativamente em maneiras de simplificar e automatizar a descoberta e a aplicação da configuração ideal de NNAPI.

Quantização

A quantização reduz o tamanho do modelo usando números inteiros de 8 bits ou números flutuantes de 16 bits em vez de números flutuantes de 32 bits para cálculo. Os tamanhos dos modelos inteiros de 8 bits são um quarto das versões flutuantes de 32 bits; Os carros flutuantes de 16 bits têm metade do tamanho. A quantização pode melhorar significativamente o desempenho, embora o processo possa comprometer alguma precisão do modelo.

Existem vários tipos de técnicas de quantização pós-treinamento disponíveis, mas, para máximo suporte e aceleração no hardware atual, recomendamos a quantização de número inteiro completo . Esta abordagem converte o peso e as operações em números inteiros. Este processo de quantização requer um conjunto de dados representativo para funcionar.

Use modelos e operações compatíveis

Se o delegado NNAPI não suportar algumas das operações ou combinações de parâmetros em um modelo, a estrutura executará apenas as partes suportadas do gráfico no acelerador. O restante é executado na CPU, o que resulta em execução dividida. Devido ao alto custo da sincronização CPU/acelerador, isso pode resultar em desempenho mais lento do que executar toda a rede apenas na CPU.

A NNAPI tem melhor desempenho quando os modelos usam apenas operações suportadas . Os seguintes modelos são conhecidos por serem compatíveis com NNAPI:

A aceleração NNAPI também não é suportada quando o modelo contém saídas dimensionadas dinamicamente. Nesse caso, você receberá um aviso como:

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

Habilitar implementação de CPU NNAPI

Um gráfico que não pode ser processado completamente por um acelerador pode recorrer à implementação da CPU NNAPI. No entanto, como normalmente tem menos desempenho do que o interpretador do TensorFlow, essa opção é desativada por padrão no delegado NNAPI para Android 10 (API de nível 29) ou superior. Para substituir esse comportamento, configure setUseNnapiCpu como true no objeto NnApiDelegate.Options .