GPU上のTensorFlowLite

TensorFlow Liteは、いくつかのハードウェアアクセラレータをサポートしています。このドキュメントでは、Android(OpenCLまたはOpenGL ES 3.1以降が必要)およびiOS(iOS 8以降が必要)でTensorFlowLiteデリゲートAPIを使用してGPUバックエンドを使用する方法について説明します。

GPUアクセラレーションのメリット

速度

GPUは、大規模に並列化可能なワークロードに対して高スループットを実現するように設計されています。したがって、これらは、多数の演算子で構成されるディープニューラルネットに最適です。各演算子は、小さなワークロードに簡単に分割して並行して実行できるいくつかの入力テンソルで動作します。この並列処理により、通常、待ち時間が短くなります。最良のシナリオでは、GPUでの推論は、以前は不可能だったリアルタイムアプリケーションに適した速度で実行される可能性があります。

正確さ

GPUは、16ビットまたは32ビットの浮動小数点数を使用して計算を行い、(CPUとは異なり)最適なパフォーマンスを得るために量子化を必要としません。精度の低下によりモデルで量子化が不可能になった場合、GPUでニューラルネットワークを実行すると、この懸念が解消される可能性があります。

エネルギー効率

GPU推論に伴うもう1つの利点は、その電力効率です。 GPUは、非常に効率的で最適化された方法で計算を実行し、CPUで実行される同じタスクよりも消費電力と発熱量が少なくなります。

サポートされている操作

GPU上のTensorFlowLiteは、16ビットおよび32ビットの浮動小数点精度で次の演算をサポートします。

  • 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

デフォルトでは、すべての操作はバージョン1でのみサポートされます。実験的な量子化サポートを有効にすると、適切なバージョンが許可されます。たとえば、ADDv2です。

基本的な使い方

Android StudioMLモデルバインディングとTensorFlowLiteインタープリターのどちらを使用しているかに応じて、Androidでモデルアクセラレーションを呼び出す方法は2つあります。

TensorFlowLiteインタープリター経由のAndroid

tensorflow-lite-gpuパッケージを、既存のdependenciesブロックの既存のtensorflow-liteパッケージと一緒に追加します。

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

次に、 TfLiteDelegateしてGPUでTfLiteDelegateTfLiteDelegateます。 Javaでは、 Interpreter.Options介してGpuDelegateを指定できます。

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

Android上TensorFlow LiteとGPUのC / C ++使用方法については、GPUのデリゲートを使用して作成することができますTfLiteGpuDelegateV2Create()として破壊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);

TfLiteGpuDelegateOptionsV2を見て、カスタムオプションを使用してデリゲートインスタンスを作成します。 TfLiteGpuDelegateOptionsV2Default()を使用してデフォルトのオプションを初期化し、必要に応じて変更できます。

Android C / C ++用のTFLiteGPUは、 Bazelビルドシステムを使用します。デリゲートは、たとえば、次のコマンドを使用して作成できます。

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

GPUでTensorFlowLiteを使用するには、 TFLGpuDelegateCreate()を介してGPUデリゲートを取得し、( Interpreter::AllocateTensors()を呼び出す代わりにInterpreter::ModifyGraphWithDelegate()ます。

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

高度な使用法

iOSの委任オプション

GPUデリゲートのコンストラクターは、オプションのstructを受け入れます。 ( Swift APIObjective-C APIC API

nullptr (C API)または何も(Objective-CおよびSwift API)をイニシャnullptr渡すと、デフォルトのオプションが設定されます(上記の基本的な使用例で説明されています)。

迅速

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

nullptrまたはデフォルトのコンストラクターを使用すると便利ですが、将来デフォルト値が変更された場合に予期しない動作が発生しないように、オプションを明示的に設定することをお勧めします。

GPUでの量子化モデルの実行

このセクションでは、GPUデリゲートが8ビットの量子化モデルを高速化する方法について説明します。これには、次のようなすべての種類の量子化が含まれます。

パフォーマンスを最適化するには、浮動小数点の入力テンソルと出力テンソルを備えたモデルを使用します。

これはどのように作動しますか?

GPUバックエンドは浮動小数点の実行のみをサポートするため、元のモデルの「浮動小数点ビュー」を指定して量子化モデルを実行します。大まかに言うと、これには次の手順が必要です。

  • 一定のテンソル(重み/バイアスなど)は、GPUメモリに一度逆量子化されます。これは、デリゲートがTFLiteインタープリターに適用されたときに発生します。

  • GPUプログラムへの入力と出力は、8ビットが量子化されている場合、推論ごとに(それぞれ)非量子化および量子化されます。これは、TFLiteの最適化されたカーネルを使用してCPU上で実行されます。

  • GPUプログラムは、操作間に量子化シミュレーターを挿入することにより、量子化された動作を模倣するように変更されています。これは、オプスが量子化中に学習した範囲に従うことをopsが期待するモデルに必要です。

この機能は、次のようにデリゲートオプションを使用して有効にできます。

アンドロイド

Android APIは、デフォルトで量子化モデルをサポートしています。無効にするには、次の手順を実行します。

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 APIは、デフォルトで量子化モデルをサポートします。無効にするには、次の手順を実行します。

迅速

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

入出力バッファー(iOS、C ++ APIのみ)

GPUで計算を行うには、GPUでデータを利用できるようにする必要があります。多くの場合、これにはメモリコピーの実行が必要です。可能であれば、CPU / GPUメモリの境界を越えないことが望ましいです。これにはかなりの時間がかかる可能性があるためです。通常、このような交差は避けられませんが、特別な場合には、どちらか一方を省略できます。

ネットワークの入力がGPUメモリにすでにロードされている画像(たとえば、カメラフィードを含むGPUテクスチャ)である場合、CPUメモリに入ることなくGPUメモリにとどまることができます。同様に、ネットワークの出力がレンダリング可能な画像の形式である場合(たとえば、 画像スタイルの転送)、画面に直接表示できます。

最高のパフォーマンスを実現するために、TensorFlow Liteを使用すると、ユーザーはTensorFlowハードウェアバッファーから直接読み取りおよび書き込みを行い、回避可能なメモリコピーをバイパスできます。

画像入力がGPUメモリにあると仮定すると、最初にMetalのMTLBufferオブジェクトに変換する必要があります。あなたは、ユーザーが準備にTfLiteTensorを関連付けることができMTLBufferTFLGpuDelegateBindMetalBufferToTensor() TFLGpuDelegateBindMetalBufferToTensor()は、 Interpreter::ModifyGraphWithDelegate()後に呼び出す必要があることに注意してください。さらに、推論出力は、デフォルトでGPUメモリからCPUメモリにコピーされます。この動作は、初期化中に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;

ヒントとコツ

  • CPUで簡単な操作の中には、GPUでは高コストになるものがあります。このような操作の1つのクラスには、さまざまな形式の形状変更操作( BATCH_TO_SPACESPACE_TO_BATCHSPACE_TO_DEPTH 、および同様の操作を含む)が含まれます。これらの操作が必要ない場合(たとえば、ネットワークアーキテクトがシステムについて推論するのを助けるために挿入されたが、それ以外の場合は出力に影響を与えない場合)、パフォーマンスのためにそれらを削除する価値があります。

  • GPUでは、テンソルデータは4チャネルにスライスされます。したがって、形状[B, H, W, 5]テンソルでの計算は、形状[B, H, W, 8]テンソルでほぼ同じように実行されますが、 [B, H, W, 8]よりも大幅に劣り[B, H, W, 4]

    • たとえば、カメラハードウェアがRGBAの画像フレームをサポートしている場合、メモリコピー(3チャンネルRGBから4チャンネルRGBXへ)を回避できるため、その4チャンネル入力のフィードは大幅に高速になります。
  • 最高のパフォーマンスを得るには、モバイル向けに最適化されたネットワークアーキテクチャを使用して分類器を再トレーニングすることを躊躇しないでください。これは、デバイス上の推論の最適化の重要な部分です。