GPU で TensorFlow Lite を使用する

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

GPU アクセラレーションの利点

速度

GPU は、大規模に実行する並列化可能なワークロードで高い処理能力を実現するように設計されています。そのため、これは多数の演算で構成されるディープニューラルネットに適しています。各演算は、より小さなワークロードに簡単に分割でき、並列に実行する入力テンソルで機能するため、通常レイテンシが低くなります。現在、最良のシナリオでは、GPU での推論は以前は利用できなかったリアルタイムアプリケーションで十分に速く実行できます。

精度

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

電力効率

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

サポートする演算子

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

  • ADD
  • AVERAGE_POOL_2D
  • CONCATENATION
  • CONV_2D
  • DEPTHWISE_CONV_2D v1-2
  • EXP
  • FULLY_CONNECTED
  • LOGISTIC
  • LSTM v2(Basic LSTM のみ)
  • MAX_POOL_2D
  • MAXIMUM
  • MINIMUM
  • MUL
  • PAD
  • PRELU
  • RELU
  • RELU6
  • RESHAPE
  • RESIZE_BILINEAR v1-3
  • SOFTMAX
  • STRIDED_SLICE
  • SUB
  • TRANSPOSE_CONV

デフォルトでは、すべての演算はバージョン 1 でのみサポートされています。実験的な量子化サポートを有効にすると、適切なバージョンが許可されます (ADD v2 など)。

基本的な使い方

Android でモデルアクセラレーションを呼び出す方法は 2 つありますが、Android Studio ML Model Binding または TensorFlow Lite インタープリタを使用しているかによって、方法は異なります。

TensorFlow Lite Interpreter を使用して Android でモデルアクセラレーションを呼び出す

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

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

次に、TfLiteDelegateを使用して GPU で TensorFlow Lite を実行します。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 C/C++ 向け TensorFlow Lite GPU を使用する場合、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++ 向け TFLite GPU では、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

注意: Interpreter::ModifyGraphWithDelegate()またはInterpreter::Invoke()を呼び出す場合、呼び出し元はその時点のスレッドにEGLContextを持ち、Interpreter::Invoke()は、同じEGLContextから呼び出す必要があります。EGLContextが存在しない場合、デリゲートは内部的に作成しますが、開発者はInterpreter::Invoke()が常にInterpreter::ModifyGraphWithDelegate()を呼び出すスレッドと同じスレッドから呼び出されるようにする必要があります。

iOS (C++)

注意:Swift/Objective-C/C のユースケースについては、GPU デリゲートガイドを参照してください。

注意:これは、bazel を使用している場合、または TensorFlow Lite を自分でビルドしている場合にのみ使用できます。C++ API は CocoaPods では使用できません。

GPU で TensorFlow Lite を使用するには、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)、デフォルトのオプションが設定されます(上記の基本的な使用例で説明されています)。

Swift

    // 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 Interpreter に適用されるときに発生します。

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

  • GPU プログラムは、演算の間に量子化シミュレータを挿入することにより、量子化された動作を模倣するように変更されます。これは、演算時にアクティベーションが量子化中に学習された境界に従うことが期待されるモデルに必要です。

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

Android

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

Swift

    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 のみ)

注意:これは、bazel を使用している場合、または TensorFlow Lite を自分でビルドしている場合にのみ使用できます。C++ API は CocoaPods では使用できません。

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;

注意: デフォルトの動作が無効になっている場合、GPU メモリから CPU メモリに推論出力をコピーするには、各出力テンソルに対してInterpreter::EnsureTensorDataIsReadable()を明示的に呼び出す必要があります。

注意: これは量子化モデルでも機能しますが、バッファは内部の逆量子化バッファにバインドされるため、float32 データを含む float32 サイズのバッファが必要です。

GPU デリゲートのシリアル化

前の初期化からの GPU カーネルコードとモデルデータのシリアル化を使用すると、GPU デリゲートの初期化のレイテンシーを 90% まで抑えることができます。この改善は、時間を節約するためにディスク容量を交換することで達成されます。この機能は、以下のサンプルコードで示されるように、いくつかの構成オプションで有効にすることができます。

C++

    TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
    options.experimental_flags |= TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION;
    options.serialization_dir = kTmpDir;
    options.model_token = kModelToken;

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

Java

    GpuDelegate delegate = new GpuDelegate(
      new GpuDelegate.Options().setSerializationParams(
        /* serializationDir= */ serializationDir,
        /* modelToken= */ modelToken));

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

シリアル化機能を使用する場合、コードが以下の実装ルールでコンパイルすることを確認してください。

  • シリアル化データを他のアプリがアクセスできないディレクトリに保存します。Android デバイスでは、現在のアプリケーションに非公開の場所にポイントする getCodeCacheDir() を使用します。
  • モデルトークンは、特定のモデルのデバイスに一意である必要があります。モデルトークンは、モデルデータからフィンガープリントを生成することで計算できます(farmhash::Fingerprint64 を使用するなど)。

注意: この機能には、シリアル化サポートを提供する OpenCL SDK が必要です。

ヒントとコツ

  • 演算によっては CPU では簡単で GPU ではコストが高くなる可能性があります。このような演算の 1 つのクラスは、BATCH_TO_SPACESPACE_TO_BATCHSPACE_TO_DEPTHなど、さまざまな形の変形演算です。ネットワークアーキテクトの論理的思考のためだけにこれらの演算がネットワークに挿入されている場合、パフォーマンスのためにそれらを削除することをお勧めします。

  • GPU では、テンソルデータは 4 チャネルにスライスされます。したがって、形状[B,H,W,5]のテンソルに対する計算は、形状[B,H,W,8]のテンソルに対する計算とほぼ同じように実行されますが、パフォーマンスは[B,H,W,4]と比べて大幅に低下します。

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