Réserve cette date! Google I / O revient du 18 au 20 mai S'inscrire maintenant
Cette page a été traduite par l'API Cloud Translation.
Switch to English

TensorFlow Lite sur GPU

TensorFlow Lite prend en charge plusieurs accélérateurs matériels. Ce document décrit comment utiliser le backend GPU à l'aide des API de délégué TensorFlow Lite sur Android (nécessite OpenCL ou OpenGL ES 3.1 et version ultérieure) et iOS (nécessite iOS 8 ou version ultérieure).

Avantages de l'accélération GPU

La vitesse

Les GPU sont conçus pour offrir un débit élevé pour des charges de travail massivement parallélisables. Ainsi, ils sont bien adaptés aux réseaux de neurones profonds, qui se composent d'un grand nombre d'opérateurs, chacun travaillant sur un ou plusieurs tenseur (s) d'entrée qui peuvent être facilement divisés en plus petites charges de travail et exécutés en parallèle. Ce parallélisme entraîne généralement une latence plus faible. Dans le meilleur des cas, l'inférence sur le GPU peut s'exécuter assez rapidement pour convenir à des applications en temps réel qui n'étaient pas possibles auparavant.

Précision

Les GPU effectuent leur calcul avec des nombres à virgule flottante de 16 bits ou 32 bits et (contrairement aux processeurs) ne nécessitent pas de quantification pour des performances optimales. Si une précision diminuée rend la quantification intenable pour vos modèles, l'exécution de votre réseau neuronal sur un GPU peut éliminer ce problème.

Efficacité énergétique

Un autre avantage de l'inférence GPU est son efficacité énergétique. Un GPU effectue des calculs de manière très efficace et optimisée, consommant moins d'énergie et générant moins de chaleur que la même tâche exécutée sur un CPU.

Opérations prises en charge

TensorFlow Lite sur GPU prend en charge les opérations suivantes en précision flottante 16 bits et 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

Par défaut, toutes les opérations ne sont prises en charge qu'à la version 1. L'activation du support de quantification expérimentale autorise les versions appropriées; par exemple, ADD v2.

Utilisation de base

Il existe deux façons d'appeler l'accélération de modèle dans Android selon que vous utilisez la liaison de modèle Android Studio ML ou l'interpréteur TensorFlow Lite.

Android via l'interpréteur TensorFlow Lite

Ajoutez le tensorflow-lite-gpu aux côtés du package tensorflow-lite existant dans le bloc de dependencies existant.

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

Ensuite, exécutez TensorFlow Lite sur GPU avec TfLiteDelegate . En Java, vous pouvez spécifier le GpuDelegate via 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 ++)

Pour l'utilisation C / C ++ du GPU TensorFlow Lite sur Android, le délégué GPU peut être créé avec TfLiteGpuDelegateV2Create() et détruit avec 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);

Jetez un œil à TfLiteGpuDelegateOptionsV2 pour créer une instance de délégué avec des options personnalisées. Vous pouvez initialiser les options par défaut avec TfLiteGpuDelegateOptionsV2Default() , puis les modifier si nécessaire.

Le GPU TFLite pour Android C / C ++ utilise le système de construction Bazel . Le délégué peut être généré, par exemple, à l'aide de la commande suivante:

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

Pour utiliser TensorFlow Lite sur GPU, récupérez le délégué GPU via TFLGpuDelegateCreate() , puis passez-le à Interpreter::ModifyGraphWithDelegate() (au lieu d'appeler 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);

Utilisation avancée

Options de délégué pour iOS

Le constructeur du délégué GPU accepte une struct d'options. ( API Swift, API Objective-C, API C )

Passer nullptr (API C) ou rien (API Objective-C et Swift) à l'initialiseur définit les options par défaut (qui sont expliquées dans l'exemple d'utilisation de base ci-dessus).

Rapide

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

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

Bien qu'il soit pratique d'utiliser nullptr ou des constructeurs par défaut, nous vous recommandons de définir explicitement les options, pour éviter tout comportement inattendu si les valeurs par défaut sont modifiées à l'avenir.

Exécution de modèles quantifiés sur GPU

Cette section explique comment le délégué GPU accélère les modèles quantifiés 8 bits. Cela inclut toutes les saveurs de quantification, y compris:

Pour optimiser les performances, utilisez des modèles dotés de tenseurs d'entrée et de sortie à virgule flottante.

Comment cela marche-t-il?

Étant donné que le backend GPU ne prend en charge que l'exécution en virgule flottante, nous exécutons des modèles quantifiés en lui donnant une `` vue en virgule flottante '' du modèle d'origine. À un niveau élevé, cela implique les étapes suivantes:

  • Les tenseurs constants (tels que les poids / biais) sont déquantifiés une fois dans la mémoire GPU. Cela se produit lorsque le délégué est appliqué à l'interpréteur TFLite.

  • Les entrées et sorties du programme GPU, si quantifiées sur 8 bits, sont déquantifiées et quantifiées (respectivement) pour chaque inférence. Cela se fait sur le processeur en utilisant les noyaux optimisés de TFLite.

  • Le programme GPU est modifié pour imiter le comportement quantifié en insérant des simulateurs de quantification entre les opérations. Ceci est nécessaire pour les modèles où les opérations s'attendent à ce que les activations suivent les limites apprises pendant la quantification.

Cette fonctionnalité peut être activée à l'aide des options de délégué comme suit:

Android

Les API Android prennent en charge les modèles quantifiés par défaut. Pour désactiver, procédez comme suit:

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

Les API iOS prennent en charge les modèles quantifiés par défaut. Pour désactiver, procédez comme suit:

Rapide

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

Objectif c

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

C

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

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Tampons d'entrée / sortie (iOS, API C ++ uniquement)

Pour effectuer des calculs sur le GPU, les données doivent être mises à disposition du GPU. Cela nécessite souvent d'effectuer une copie mémoire. Il est souhaitable de ne pas franchir la limite de la mémoire CPU / GPU si possible, car cela peut prendre un temps considérable. Habituellement, un tel croisement est inévitable, mais dans certains cas particuliers, l'un ou l'autre peut être omis.

Si l'entrée du réseau est une image déjà chargée dans la mémoire du GPU (par exemple, une texture GPU contenant le flux de la caméra), elle peut rester dans la mémoire du GPU sans jamais entrer dans la mémoire du CPU. De même, si la sortie du réseau est sous la forme d'une image pouvant être rendue (par exemple, un transfert de style d'image ), elle peut être directement affichée à l'écran.

Pour obtenir les meilleures performances, TensorFlow Lite permet aux utilisateurs de lire et d'écrire directement dans le tampon matériel TensorFlow et de contourner les copies de mémoire évitables.

En supposant que l'entrée d'image se trouve dans la mémoire du GPU, elle doit d'abord être convertie en un objet MTLBuffer pour Metal. Vous pouvez associer un TfLiteTensor à un MTLBuffer préparé par l' MTLBuffer avec TFLGpuDelegateBindMetalBufferToTensor() . Notez que TFLGpuDelegateBindMetalBufferToTensor() doit être appelé après Interpreter::ModifyGraphWithDelegate() . De plus, la sortie d'inférence est, par défaut, copiée de la mémoire GPU vers la mémoire CPU. Ce comportement peut être désactivé en appelant Interpreter::SetAllowBufferHandleOutput(true) lors de l'initialisation.

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

Trucs et astuces

  • Certaines opérations qui sont triviales sur le processeur peuvent être coûteuses sur un GPU. Une classe d'une telle opération comprend diverses formes d'opérations de remodelage (y compris BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH et une opération similaire). Si ces opérations ne sont pas nécessaires (par exemple, elles ont été insérées pour aider l'architecte réseau à raisonner sur le système mais n'affectent pas autrement la sortie), il vaut la peine de les supprimer pour les performances.

  • Sur un GPU, les données du tenseur sont découpées en 4 canaux. Ainsi, un calcul sur un tenseur de forme [B, H, W, 5] effectuera à peu près la même chose sur un tenseur de forme [B, H, W, 8] , mais nettement pire que [B, H, W, 4] .

    • Par exemple, si le matériel de la caméra prend en charge les images dans RGBA, l'alimentation de cette entrée à 4 canaux est beaucoup plus rapide, car une copie de la mémoire (de RVB à 3 canaux à RGBX à 4 canaux) peut être évitée.
  • Pour de meilleures performances, n'hésitez pas à réentraîner votre classificateur avec une architecture réseau optimisée pour les mobiles. C'est une partie importante de l'optimisation de l'inférence sur l'appareil.