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:
- Modèles formés avec une formation sensible à la quantification
- Quantification de la plage dynamique après l'entraînement
- Quantification entière après l'entraînement
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.