¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

TensorFlow Lite en GPU

TensorFlow Lite admite varios aceleradores de hardware. Este documento describe cómo usar el backend de la GPU con las API delegadas de TensorFlow Lite en Android (requiere OpenCL u OpenGL ES 3.1 y superior) e iOS (requiere iOS 8 o posterior).

Beneficios de la aceleración de la GPU

Velocidad

Las GPU están diseñadas para tener un alto rendimiento para cargas de trabajo masivamente paralelizables. Por lo tanto, son adecuados para redes neuronales profundas, que constan de una gran cantidad de operadores, cada uno de los cuales trabaja en algún tensor de entrada que se puede dividir fácilmente en cargas de trabajo más pequeñas y llevar a cabo en paralelo. Este paralelismo suele dar como resultado una latencia más baja. En el mejor de los casos, la inferencia en la GPU puede ejecutarse lo suficientemente rápido como para ser adecuada para aplicaciones en tiempo real que antes no eran posibles.

Precisión

Las GPU realizan sus cálculos con números de punto flotante de 16 bits o 32 bits y (a diferencia de las CPU) no requieren cuantificación para un rendimiento óptimo. Si la menor precisión hace que la cuantificación sea insostenible para sus modelos, ejecutar su red neuronal en una GPU puede eliminar esta preocupación.

Eficiencia energética

Otro beneficio que viene con la inferencia de GPU es su eficiencia energética. Una GPU realiza cálculos de una manera muy eficiente y optimizada, consumiendo menos energía y generando menos calor que la misma tarea ejecutada en una CPU.

Operaciones apoyadas

TensorFlow Lite en GPU admite las siguientes operaciones en precisión flotante de 16 y 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

De forma predeterminada, todas las operaciones solo se admiten en la versión 1. Habilitar el soporte de cuantificación experimental permite las versiones apropiadas; por ejemplo, ADD v2.

Uso básico

Hay dos formas de invocar la aceleración del modelo en Android, dependiendo de si usa el enlace de modelo de Android Studio ML o el intérprete de TensorFlow Lite.

Android a través del intérprete de TensorFlow Lite

Agregue el tensorflow-lite-gpu junto con el paquete tensorflow-lite existente en el bloque de dependencies existente.

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

Luego, ejecute TensorFlow Lite en GPU con TfLiteDelegate . En Java, puede especificar GpuDelegate través 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 el uso de C / C ++ de la GPU TensorFlow Lite en Android, el delegado de la GPU se puede crear con TfLiteGpuDelegateV2Create() y destruir con TfLiteGpuDelegateV2Delete() .

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

// NEW: Prepare GPU delegate.
auto* delegate = TfLiteGpuDelegateV2Create(/*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));

// NEW: Clean up.
TfLiteGpuDelegateV2Delete(delegate);

Eche un vistazo a TfLiteGpuDelegateOptionsV2 para crear una instancia de delegado con opciones personalizadas. Puede inicializar las opciones predeterminadas con TfLiteGpuDelegateOptionsV2Default() y luego modificarlas según sea necesario.

TFLite GPU para Android C / C ++ utiliza el sistema de compilación Bazel . El delegado se puede construir, por ejemplo, usando el siguiente 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 TensorFlow Lite en GPU, obtenga el delegado de GPU a través de TFLGpuDelegateCreate() y luego páselo a Interpreter::ModifyGraphWithDelegate() (en lugar de llamar a 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 avanzado

Opciones de delegado para iOS

El constructor para delegado de GPU acepta una struct de opciones. ( API Swift , API Objective-C , API C )

Pasar nullptr (API C) o nada (Objective-C y Swift API) al inicializador establece las opciones predeterminadas (que se explican en el ejemplo de uso básico anterior).

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()
      

C objetivo

    // 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);
      

Si bien es conveniente utilizar nullptr o constructores predeterminados, le recomendamos que configure explícitamente las opciones para evitar cualquier comportamiento inesperado si los valores predeterminados se cambian en el futuro.

Ejecución de modelos cuantificados en GPU

Esta sección explica cómo el delegado de GPU acelera los modelos cuantificados de 8 bits. Esto incluye todos los tipos de cuantificación, incluidos:

Para optimizar el rendimiento, utilice modelos que tengan tensores de entrada y salida de punto flotante.

¿Como funciona esto?

Dado que el backend de la GPU solo admite la ejecución de punto flotante, ejecutamos modelos cuantificados dándole una 'vista de punto flotante' del modelo original. A alto nivel, esto implica los siguientes pasos:

  • Los tensores constantes (como pesos / sesgos) se descuantifican una vez en la memoria de la GPU. Esto sucede cuando el delegado se aplica al intérprete de TFLite.

  • Las entradas y salidas al programa GPU, si se cuantifican en 8 bits, se descuantifican y cuantifican (respectivamente) para cada inferencia. Esto se hace en la CPU utilizando los núcleos optimizados de TFLite.

  • El programa de la GPU se modifica para imitar el comportamiento cuantificado insertando simuladores de cuantificación entre operaciones. Esto es necesario para los modelos en los que las operaciones esperan que las activaciones sigan los límites aprendidos durante la cuantificación.

Esta función se puede habilitar usando las opciones de delegado de la siguiente manera:

Androide

Las API de Android admiten modelos cuantificados de forma predeterminada. Para deshabilitar, haga lo siguiente:

API de C ++

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

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

API de Java

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

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

iOS

Las API de iOS admiten modelos cuantificados de forma predeterminada. Para deshabilitar, haga lo siguiente:

Rápido

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

C objetivo

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

C

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

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Búferes de entrada / salida (solo iOS, API de C ++)

Para realizar cálculos en la GPU, los datos deben estar disponibles para la GPU. Esto a menudo requiere realizar una copia de la memoria. Es deseable no cruzar el límite de la memoria CPU / GPU si es posible, ya que esto puede llevar una cantidad de tiempo significativa. Por lo general, dicho cruce es inevitable, pero en algunos casos especiales, se puede omitir uno u otro.

Si la entrada de la red es una imagen ya cargada en la memoria de la GPU (por ejemplo, una textura de la GPU que contiene la alimentación de la cámara), puede permanecer en la memoria de la GPU sin entrar en la memoria de la CPU. De manera similar, si la salida de la red tiene la forma de una imagen renderizable (por ejemplo, transferencia de estilo de imagen ), se puede mostrar directamente en la pantalla.

Para lograr el mejor rendimiento, TensorFlow Lite permite a los usuarios leer y escribir directamente en el búfer de hardware de TensorFlow y omitir copias de memoria evitables.

Suponiendo que la entrada de la imagen está en la memoria de la GPU, primero debe convertirse en un objeto MTLBuffer para Metal. Puede asociar un TfLiteTensor a un MTLBuffer preparado por el MTLBuffer con TFLGpuDelegateBindMetalBufferToTensor() . Tenga en cuenta que TFLGpuDelegateBindMetalBufferToTensor() debe llamarse después de Interpreter::ModifyGraphWithDelegate() . Además, la salida de inferencia se copia, de forma predeterminada, de la memoria de la GPU a la memoria de la CPU. Este comportamiento se puede desactivar llamando a Interpreter::SetAllowBufferHandleOutput(true) durante la inicialización.

#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;

Consejos y trucos

  • Algunas operaciones que son triviales en la CPU pueden tener un alto costo en una GPU. Una clase de dicha operación incluye varias formas de operaciones de remodelación (incluidas BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH y operaciones similares). Si estas operaciones no son necesarias (por ejemplo, se insertaron para ayudar al arquitecto de red a razonar sobre el sistema, pero no afectan a la salida), vale la pena eliminarlas para mejorar el rendimiento.

  • En una GPU, los datos de los tensores se dividen en 4 canales. Por lo tanto, un cálculo en un tensor de forma [B, H, W, 5] funcionará aproximadamente igual en un tensor de forma [B, H, W, 8] , pero significativamente peor que [B, H, W, 4] .

    • Por ejemplo, si el hardware de la cámara admite cuadros de imagen en RGBA, la alimentación de esa entrada de 4 canales es significativamente más rápida, porque se puede evitar una copia de memoria (de RGB de 3 canales a RGBX de 4 canales).
  • Para un mejor rendimiento, no dude en volver a entrenar su clasificador con una arquitectura de red optimizada para dispositivos móviles. Esa es una parte importante de la optimización para la inferencia en el dispositivo.