TensorFlow Lite auf GPU

TensorFlow Lite unterstützt mehrere Hardwarebeschleuniger. In diesem Dokument wird beschrieben, wie Sie das GPU-Backend mithilfe der TensorFlow Lite-Delegaten-APIs unter Android (erfordert OpenCL oder OpenGL ES 3.1 und höher) und iOS (erfordert iOS 8 oder höher) verwenden.

Vorteile der GPU-Beschleunigung

Geschwindigkeit

GPUs sind für einen hohen Durchsatz bei massiv parallelisierbaren Workloads ausgelegt. Sie eignen sich daher gut für tiefe neuronale Netze, die aus einer großen Anzahl von Operatoren bestehen, die jeweils an einem oder mehreren Eingangstensoren arbeiten, die leicht in kleinere Workloads unterteilt und parallel ausgeführt werden können. Diese Parallelität führt typischerweise zu einer geringeren Latenz. Im besten Fall kann die Inferenz auf der GPU schnell genug ausgeführt werden, um für Echtzeitanwendungen geeignet zu sein, die zuvor nicht möglich waren.

Richtigkeit

GPUs berechnen mit 16-Bit- oder 32-Bit-Gleitkommazahlen und erfordern (im Gegensatz zu den CPUs) keine Quantisierung für eine optimale Leistung. Wenn eine verringerte Genauigkeit die Quantisierung für Ihre Modelle unhaltbar macht, kann dieses Problem durch Ausführen Ihres neuronalen Netzwerks auf einer GPU beseitigt werden.

Energieeffizienz

Ein weiterer Vorteil der GPU-Inferenz ist die Energieeffizienz. Eine GPU führt Berechnungen auf sehr effiziente und optimierte Weise durch, verbraucht weniger Strom und erzeugt weniger Wärme als dieselbe Aufgabe, die auf einer CPU ausgeführt wird.

Unterstützte Operationen

TensorFlow Lite auf der GPU unterstützt die folgenden Operationen mit 16-Bit- und 32-Bit-Float-Genauigkeit:

  • 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

Standardmäßig werden alle Operationen nur in Version 1 unterstützt. Durch Aktivieren der experimentellen Quantisierungsunterstützung werden die entsprechenden Versionen zugelassen. Beispiel: ADD v2.

Grundlegende Verwendung

Es gibt zwei Möglichkeiten, die Modellbeschleunigung in Android aufzurufen, je nachdem, ob Sie Android Studio ML Model Binding oder TensorFlow Lite Interpreter verwenden.

Android über TensorFlow Lite Interpreter

Fügen Sie das tensorflow-lite-gpu Paket neben dem vorhandenen tensorflow-lite Paket im vorhandenen dependencies .

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

Führen Sie dann TensorFlow Lite auf einer GPU mit TfLiteDelegate . In Java können Sie das GpuDelegate über Interpreter.Options angeben.

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

Für die C / C ++ - Verwendung der TensorFlow Lite-GPU unter Android kann der GPU-Delegat mit TfLiteGpuDelegateV2Create() und mit 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);

Schauen Sie sich TfLiteGpuDelegateOptionsV2 an, um eine Delegateninstanz mit benutzerdefinierten Optionen zu erstellen. Sie können die Standardoptionen mit TfLiteGpuDelegateOptionsV2Default() initialisieren und sie dann nach Bedarf ändern.

Die TFLite-GPU für Android C / C ++ verwendet das Bazel- Build-System. Der Delegat kann beispielsweise mit dem folgenden Befehl erstellt werden:

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

Um TensorFlow Lite auf GPU zu verwenden, erhalten die GPU Delegierten über TFLGpuDelegateCreate() und übergeben es dann an Interpreter::ModifyGraphWithDelegate() (statt Aufruf 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);

Erweiterte Verwendung

Optionen für iOS delegieren

Der Konstruktor für den GPU-Delegaten akzeptiert eine struct von Optionen. ( Schnelle API , Objective-C-API , C-API )

Wenn Sie nullptr (C-API) oder nichts (Objective-C und Swift-API) an den Initialisierer übergeben, werden die Standardoptionen festgelegt (die im obigen Beispiel für die grundlegende Verwendung erläutert werden).

Schnell

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

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

Es ist zwar praktisch, nullptr oder Standardkonstruktoren zu verwenden, wir empfehlen jedoch, die Optionen explizit nullptr , um unerwartetes Verhalten zu vermeiden, wenn Standardwerte in Zukunft geändert werden.

Ausführen quantisierter Modelle auf einer GPU

In diesem Abschnitt wird erläutert, wie der GPU-Delegat quantisierte 8-Bit-Modelle beschleunigt. Dies umfasst alle Arten der Quantisierung, einschließlich:

Verwenden Sie zur Optimierung der Leistung Modelle mit Gleitkomma-Eingangs- und Ausgangstensoren.

Wie funktioniert das?

Da das GPU-Backend nur die Gleitkomma-Ausführung unterstützt, führen wir quantisierte Modelle aus, indem wir ihm eine Gleitkomma-Ansicht des ursprünglichen Modells geben. Auf hoher Ebene umfasst dies die folgenden Schritte:

  • Konstante Tensoren (wie Gewichte / Vorspannungen) werden einmal in den GPU-Speicher dequantisiert. Dies geschieht, wenn der Delegat auf den TFLite-Interpreter angewendet wird.

  • Ein- und Ausgänge in das GPU-Programm werden, wenn sie 8-Bit quantisiert sind, für jede Inferenz dequantisiert bzw. quantisiert. Dies erfolgt auf der CPU mithilfe der optimierten Kernel von TFLite.

  • Das GPU-Programm wird so modifiziert, dass es quantisiertes Verhalten nachahmt, indem Quantisierungssimulatoren zwischen Operationen eingefügt werden. Dies ist für Modelle erforderlich, bei denen Ops erwarten, dass Aktivierungen den während der Quantisierung erlernten Grenzen folgen.

Diese Funktion kann mithilfe der folgenden Delegatenoptionen aktiviert werden:

Android

Android-APIs unterstützen standardisiert quantisierte Modelle. Gehen Sie zum Deaktivieren wie folgt vor:

C ++ API

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

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

Java API

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

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

iOS

iOS-APIs unterstützen standardisiert quantisierte Modelle. Gehen Sie zum Deaktivieren wie folgt vor:

Schnell

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

Ziel c

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

C.

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

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Eingabe- / Ausgabepuffer (nur iOS, C ++ API)

Um Berechnungen auf der GPU durchführen zu können, müssen der GPU Daten zur Verfügung gestellt werden. Dies erfordert häufig eine Speicherkopie. Es ist wünschenswert, die CPU / GPU-Speichergrenze nach Möglichkeit nicht zu überschreiten, da dies viel Zeit in Anspruch nehmen kann. Normalerweise ist eine solche Kreuzung unvermeidlich, aber in einigen besonderen Fällen kann die eine oder andere weggelassen werden.

Wenn der Netzwerkeingang ein Bild ist, das bereits in den GPU-Speicher geladen wurde (z. B. eine GPU-Textur, die den Kamera-Feed enthält), kann er im GPU-Speicher verbleiben, ohne jemals in den CPU-Speicher zu gelangen. Wenn die Netzwerkausgabe in Form eines renderbaren Bildes (z. B. Bildstilübertragung ) vorliegt, kann sie ebenfalls direkt auf dem Bildschirm angezeigt werden.

Um die beste Leistung zu erzielen, können Benutzer mit TensorFlow Lite direkt aus dem TensorFlow-Hardwarepuffer lesen und in diesen schreiben und vermeidbare Speicherkopien umgehen.

Angenommen, die Bildeingabe befindet sich im GPU-Speicher, muss sie zuerst in ein MTLBuffer Objekt für Metal konvertiert werden. Sie können einen TfLiteTensor einem vom Benutzer vorbereiteten MTLBuffer mit TFLGpuDelegateBindMetalBufferToTensor() . Beachten Sie, dass TFLGpuDelegateBindMetalBufferToTensor() nach Interpreter::ModifyGraphWithDelegate() aufgerufen werden muss. Darüber hinaus wird die Inferenzausgabe standardmäßig vom GPU-Speicher in den CPU-Speicher kopiert. Dieses Verhalten kann durch Aufrufen von Interpreter::SetAllowBufferHandleOutput(true) während der Initialisierung Interpreter::SetAllowBufferHandleOutput(true) .

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

Tipps und Tricks

  • Einige Operationen, die auf der CPU trivial sind, können auf einer GPU hohe Kosten verursachen. Eine Klasse solcher Operationen umfasst verschiedene Formen von BATCH_TO_SPACE (einschließlich BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH und ähnlicher Operationen). Wenn diese Vorgänge nicht erforderlich sind (z. B. wurden sie eingefügt, um dem Netzwerkarchitekten zu helfen, über das System nachzudenken, die Ausgabe jedoch nicht anderweitig zu beeinflussen), sollten sie aus Gründen der Leistung entfernt werden.

  • Auf einer GPU werden Tensordaten in 4 Kanäle unterteilt. Somit wird eine Berechnung auf einem Tensor der Form [B, H, W, 5] ungefähr gleich auf einem Tensor der Form [B, H, W, 8] , jedoch signifikant schlechter als [B, H, W, 4] .

    • Wenn die Kamerahardware beispielsweise Bilderrahmen in RGBA unterstützt, ist die Einspeisung dieses 4-Kanal-Eingangs erheblich schneller, da eine Speicherkopie (von 3-Kanal-RGB auf 4-Kanal-RGBX) vermieden werden kann.
  • Zögern Sie nicht, Ihren Klassifikator mit einer für Mobilgeräte optimierten Netzwerkarchitektur neu zu trainieren, um eine optimale Leistung zu erzielen. Dies ist ein wesentlicher Teil der Optimierung für die Inferenz auf dem Gerät.