O Dia da Comunidade de ML é dia 9 de novembro! Junte-nos para atualização de TensorFlow, JAX, e mais Saiba mais

TensorFlow Lite na GPU

O TensorFlow Lite é compatível com vários aceleradores de hardware. Este documento descreve como usar o back-end da GPU usando as APIs delegadas do TensorFlow Lite no Android (requer OpenCL ou OpenGL ES 3.1 e superior) e iOS (requer iOS 8 ou superior).

Benefícios da aceleração de GPU

Velocidade

As GPUs são projetadas para ter alto rendimento para cargas de trabalho massivamente paralelizáveis. Assim, eles são adequados para redes neurais profundas, que consistem em um grande número de operadores, cada um trabalhando em alguns tensores de entrada que podem ser facilmente divididos em cargas de trabalho menores e executados em paralelo. Esse paralelismo normalmente resulta em menor latência. No melhor cenário, a inferência na GPU pode ser executada com rapidez suficiente para se tornar adequada para aplicativos em tempo real que não eram possíveis anteriormente.

Precisão

As GPUs fazem seus cálculos com números de ponto flutuante de 16 ou 32 bits e (ao contrário das CPUs) não exigem quantização para um desempenho ideal. Se a precisão diminuída tornou a quantização insustentável para seus modelos, executar sua rede neural em uma GPU pode eliminar essa preocupação.

Eficiência energética

Outro benefício que vem com a inferência de GPU é a eficiência de energia. Uma GPU realiza cálculos de forma muito eficiente e otimizada, consumindo menos energia e gerando menos calor do que a mesma tarefa executada em uma CPU.

Operações com suporte

O TensorFlow Lite na GPU é compatível com as seguintes operações na precisão de flutuação de 16 e 32 bits:

  • ADD
  • AVERAGE_POOL_2D
  • CONCATENATION
  • CONV_2D
  • DEPTHWISE_CONV_2D v1-2
  • EXP
  • FULLY_CONNECTED
  • LOGISTIC
  • LSTM v2 (Basic LSTM only)
  • MAX_POOL_2D
  • MAXIMUM
  • MINIMUM
  • MUL
  • PAD
  • PRELU
  • RELU
  • RELU6
  • RESHAPE
  • RESIZE_BILINEAR v1-3
  • SOFTMAX
  • STRIDED_SLICE
  • SUB
  • TRANSPOSE_CONV

Por padrão, todos os ops são suportados apenas na versão 1. Ativar o suporte de quantização experimental permite as versões apropriadas; por exemplo, ADD v2.

Uso básico

Existem duas maneiras de invocar a aceleração de modelo no Android, dependendo se você estiver usando o Android Studio ML Model Binding ou o TensorFlow Lite Interpreter.

Android via TensorFlow Lite Interpreter

Adicione o tensorflow-lite-gpu ao lado do pacote tensorflow-lite existente no bloco de dependencies existente.

dependencies {
    ...
    implementation 'org.tensorflow:tensorflow-lite:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
}

Em seguida, execute o TensorFlow Lite na GPU com TfLiteDelegate . Em Java, você pode especificar o GpuDelegate por meio de Interpreter.Options .

Kotlin

    import org.tensorflow.lite.Interpreter
    import org.tensorflow.lite.gpu.CompatibilityList
    import org.tensorflow.lite.gpu.GpuDelegate

    val compatList = CompatibilityList()

    val options = Interpreter.Options().apply{
        if(compatList.isDelegateSupportedOnThisDevice){
            // if the device has a supported GPU, add the GPU delegate
            val delegateOptions = compatList.bestOptionsForThisDevice
            this.addDelegate(GpuDelegate(delegateOptions))
        } else {
            // if the GPU is not supported, run on 4 threads
            this.setNumThreads(4)
        }
    }

    val interpreter = Interpreter(model, options)

    // Run inference
    writeToInput(input)
    interpreter.run(input, output)
    readFromOutput(output)
      

Java

    import org.tensorflow.lite.Interpreter;
    import org.tensorflow.lite.gpu.CompatibilityList;
    import org.tensorflow.lite.gpu.GpuDelegate;

    // Initialize interpreter with GPU delegate
    Interpreter.Options options = new Interpreter.Options();
    CompatibilityList compatList = CompatibilityList();

    if(compatList.isDelegateSupportedOnThisDevice()){
        // if the device has a supported GPU, add the GPU delegate
        GpuDelegate.Options delegateOptions = compatList.getBestOptionsForThisDevice();
        GpuDelegate gpuDelegate = new GpuDelegate(delegateOptions);
        options.addDelegate(gpuDelegate);
    } else {
        // if the GPU is not supported, run on 4 threads
        options.setNumThreads(4);
    }

    Interpreter interpreter = new Interpreter(model, options);

    // Run inference
    writeToInput(input);
    interpreter.run(input, output);
    readFromOutput(output);
      

Android (C / C ++)

Para uso C / C ++ da GPU TensorFlow Lite no Android, o delegado da GPU pode ser criado com TfLiteGpuDelegateV2Create() e destruído com TfLiteGpuDelegateV2Delete() .

0f Budapd1900

Dê uma olhada em TfLiteGpuDelegateOptionsV2 para criar uma instância de delegado com opções personalizadas. Você pode inicializar as opções padrão com TfLiteGpuDelegateOptionsV2Default() e, em seguida, modificá-las conforme necessário.

A GPU TFLite para Android C / C ++ usa o sistema de compilação Bazel . O delegado pode ser criado, por exemplo, usando o seguinte comando:

bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:delegate                           # for static library
bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so  # for dynamic library

iOS (C ++)

Para usar o TensorFlow Lite na GPU, obtenha o delegado da GPU via TFLGpuDelegateCreate() e, em seguida, passe-o para Interpreter::ModifyGraphWithDelegate() (em vez de chamar Interpreter::AllocateTensors() ).

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
tflite::ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.

auto* delegate = TFLGpuDelegateCreate(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// Clean up.
TFLGpuDelegateDelete(delegate);

Uso avançado

Opções de delegação para iOS

Construtor para delegado GPU aceita uma struct de opções. ( API Swift , API Objective-C , API C )

Passar nullptr (C API) ou nada (Objective-C e Swift API) para o inicializador define as opções padrão (que são explicadas no exemplo de Uso Básico acima).

Rápido

    // THIS:
    var options = MetalDelegate.Options()
    options.isPrecisionLossAllowed = false
    options.waitType = .passive
    options.isQuantizationEnabled = true
    let delegate = MetalDelegate(options: options)

    // IS THE SAME AS THIS:
    let delegate = MetalDelegate()
      

Objective-C

    // THIS:
    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.precisionLossAllowed = false;
    options.waitType = TFLMetalDelegateThreadWaitTypePassive;
    options.quantizationEnabled = true;

    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] initWithOptions:options];

    // IS THE SAME AS THIS:
    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] init];
      

C

    // THIS:
    const TFLGpuDelegateOptions options = {
      .allow_precision_loss = false,
      .wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypePassive,
      .enable_quantization = true,
    };

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);

    // IS THE SAME AS THIS:
    TfLiteDelegate* delegate = TFLGpuDelegateCreate(nullptr);
      

Embora seja conveniente usar nullptr ou construtores padrão, recomendamos que você defina explicitamente as opções, para evitar qualquer comportamento inesperado se os valores padrão forem alterados no futuro.

Executando modelos quantizados na GPU

Esta seção explica como o delegado da GPU acelera modelos quantizados de 8 bits. Isso inclui todos os tipos de quantização, incluindo:

Para otimizar o desempenho, use modelos com tensores de entrada e saída de ponto flutuante.

Como é que isso funciona?

Como o back-end da GPU só oferece suporte à execução de ponto flutuante, executamos modelos quantizados dando a ele uma 'visualização de ponto flutuante' do modelo original. Em um alto nível, isso envolve as seguintes etapas:

  • Tensores constantes (como pesos / vieses) são desquantizados uma vez na memória da GPU. Isso acontece quando o delegado é aplicado ao intérprete TFLite.

  • As entradas e saídas do programa GPU, se quantizadas em 8 bits, são desquantizadas e quantizadas (respectivamente) para cada inferência. Isso é feito na CPU usando os kernels otimizados do TFLite.

  • O programa da GPU é modificado para imitar o comportamento quantizado inserindo simuladores de quantização entre as operações. Isso é necessário para modelos onde os ops esperam que as ativações sigam os limites aprendidos durante a quantização.

Este recurso pode ser ativado usando as opções de delegado da seguinte maneira:

Android

As APIs do Android suportam modelos quantizados por padrão. Para desativar, faça o seguinte:

API C ++

TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
options.experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE;

auto* delegate = TfLiteGpuDelegateV2Create(options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

API Java

GpuDelegate delegate = new GpuDelegate(new GpuDelegate.Options().setQuantizedModelsAllowed(false));

Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);

iOS

As APIs do iOS oferecem suporte a modelos quantizados por padrão. Para desativar, faça o seguinte:

Rápido

    var options = MetalDelegate.Options()
    options.isQuantizationEnabled = false
    let delegate = MetalDelegate(options: options)
      

Objective-C

    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.quantizationEnabled = false;
      

C

    TFLGpuDelegateOptions options = TFLGpuDelegateOptionsDefault();
    options.enable_quantization = false;

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Buffers de entrada / saída (iOS, API C ++ apenas)

Para fazer cálculos na GPU, os dados devem ser disponibilizados para a GPU. Isso geralmente requer a execução de uma cópia da memória. É desejável não cruzar o limite de memória da CPU / GPU, se possível, pois isso pode levar uma quantidade significativa de tempo. Normalmente, esse cruzamento é inevitável, mas em alguns casos especiais, um ou outro pode ser omitido.

Se a entrada da rede for uma imagem já carregada na memória da GPU (por exemplo, uma textura da GPU contendo o feed da câmera), ela pode permanecer na memória da GPU sem nunca entrar na memória da CPU. Da mesma forma, se a saída da rede estiver na forma de uma imagem renderizável (por exemplo, transferência de estilo de imagem ), ela pode ser exibida diretamente na tela.

Para obter o melhor desempenho, o TensorFlow Lite possibilita que os usuários leiam e gravem diretamente no buffer de hardware do TensorFlow e ignorem as cópias de memória evitáveis.

Supondo que a entrada da imagem esteja na memória da GPU, ela deve primeiro ser convertida em um objeto MTLBuffer para Metal. Você pode associar um TfLiteTensor a um MTLBuffer preparado pelo usuário com TFLGpuDelegateBindMetalBufferToTensor() . Observe que TFLGpuDelegateBindMetalBufferToTensor() deve ser chamado após Interpreter::ModifyGraphWithDelegate() . Além disso, a saída de inferência é, por padrão, copiada da memória da GPU para a memória da CPU. Esse comportamento pode ser desativado chamando Interpreter::SetAllowBufferHandleOutput(true) durante a inicialização.

#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"

// ...

// Prepare GPU delegate.
auto* delegate = TFLGpuDelegateCreate(nullptr);

if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

interpreter->SetAllowBufferHandleOutput(true);  // disable default gpu->cpu copy
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->inputs()[0], user_provided_input_buffer)) {
  return false;
}
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->outputs()[0], user_provided_output_buffer)) {
  return false;
}

// Run inference.
if (interpreter->Invoke() != kTfLiteOk) return false;

Dicas e truques

  • Algumas operações que são triviais na CPU podem ter alto custo em uma GPU. Uma classe de tal operação inclui várias formas de operações de remodelagem (incluindo BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH e operação semelhante). Se essas operações não forem necessárias (por exemplo, foram inseridas para ajudar o arquiteto de rede a raciocinar sobre o sistema, mas não afetam a saída de outra forma), vale a pena removê-las para desempenho.

  • Em uma GPU, os dados do tensor são divididos em 4 canais. Assim, um cálculo em um tensor de forma [B, H, W, 5] terá aproximadamente o mesmo desempenho em um tensor de forma [B, H, W, 8] , mas significativamente pior que [B, H, W, 4]

    • Por exemplo, se o hardware da câmera suporta quadros de imagem em RGBA, alimentar essa entrada de 4 canais é significativamente mais rápido, porque uma cópia de memória (de RGB de 3 canais para RGBX de 4 canais) pode ser evitada.
  • Para obter o melhor desempenho, não hesite em treinar novamente seu classificador com arquitetura de rede otimizada para dispositivos móveis. Essa é uma parte significativa da otimização para inferência no dispositivo.