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 API、Objective-C API、C 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 をユーザーが準備したMTLBuffer
にTFLGpuDelegateBindMetalBufferToTensor()
を関連付けることができます。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_SPACE
、SPACE_TO_BATCH
、SPACE_TO_DEPTH
など、さまざまな形の変形演算です。ネットワークアーキテクトの論理的思考のためだけにこれらの演算がネットワークに挿入されている場合、パフォーマンスのためにそれらを削除することをお勧めします。GPU では、テンソルデータは 4 チャネルにスライスされます。したがって、形状
[B,H,W,5]
のテンソルに対する計算は、形状[B,H,W,8]
のテンソルに対する計算とほぼ同じように実行されますが、パフォーマンスは[B,H,W,4]
と比べて大幅に低下します。- たとえば、カメラハードウェアが RGBA の画像フレームをサポートしている場合、メモリコピー (3 チャネル RGB から 4 チャネル RGBX へ) を回避できるため、4 チャネル入力のフィードは大幅に速くなります。
最高のパフォーマンスを得るには、モバイル向けに最適化されたネットワークアーキテクチャで分類器を再トレーニングします。これは、デバイス上の推論の最適化の重要な部分です。