TensorFlow Liteデリゲートとは何ですか?
TensorFlow Liteデリゲートを使用すると、モデル(一部または全体)を別のエグゼキュータで実行できます。このメカニズムは、推論のためにGPUやEdge TPU(テンソルプロセッシングユニット)などのさまざまなオンデバイスアクセラレータを活用できます。これにより、開発者はデフォルトのTFLiteから柔軟で分離された方法を使用して、推論を高速化できます。
以下の図は、代表者をまとめたものです。詳細については、以下のセクションを参照してください。
カスタムデリゲートはいつ作成する必要がありますか?
TensorFlow Liteには、GPU、DSP、EdgeTPUなどのターゲットアクセラレータやAndroidNNAPIなどのフレームワーク用のさまざまなデリゲートがあります。
独自のデリゲートを作成すると、次のシナリオで役立ちます。
- 既存のデリゲートでサポートされていない新しいML推論エンジンを統合したいとします。
- 既知のシナリオの実行時間を改善するカスタムハードウェアアクセラレータがあります。
- 特定のモデルを高速化できるCPU最適化(オペレーターの融合など)を開発しています。
デリゲートはどのように機能しますか?
次のような単純なモデルグラフと、Conv2DおよびMean操作の実装がより高速なデリゲート「MyDelegate」について考えてみます。
この「MyDelegate」を適用すると、元のTensorFlowLiteグラフが次のように更新されます。
上記のグラフは、TensorFlowLiteが2つのルールに従って元のグラフを分割するときに取得されます。
- デリゲートによって処理される可能性のある特定の操作は、操作間の元のコンピューティングワークフローの依存関係を満たしながら、パーティションに配置されます。
- 委任される各パーティションには、委任によって処理されない入力ノードと出力ノードのみがあります。
デリゲートによって処理される各パーティションは、invoke呼び出しでパーティションを評価する元のグラフのデリゲートノード(デリゲートカーネルと呼ばれることもあります)に置き換えられます。
モデルによっては、最終的なグラフが1つ以上のノードになる可能性があります。後者は、一部の操作がデリゲートによってサポートされていないことを意味します。一般に、デリゲートからメイングラフに切り替えるたびに、デリゲートされたサブグラフからメイングラフに結果を渡すためのオーバーヘッドがあり、メモリが原因で結果が生じるため、デリゲートによって複数のパーティションが処理されることは望ましくありません。コピー(たとえば、GPUからCPUへ)。このようなオーバーヘッドは、特に大量のメモリコピーがある場合に、パフォーマンスの向上を相殺する可能性があります。
独自のカスタムデリゲートの実装
デリゲートを追加するための推奨される方法は、 SimpleDelegateAPIを使用することです。
新しいデリゲートを作成するには、2つのインターフェースを実装し、インターフェースメソッドに独自の実装を提供する必要があります。
1- SimpleDelegateInterface
このクラスは、サポートされている操作であるデリゲートの機能と、デリゲートされたグラフをカプセル化するカーネルを作成するためのファクトリクラスを表します。詳細については、このC++ヘッダーファイルで定義されているインターフェイスを参照してください。コード内のコメントは、各APIを詳細に説明しています。
2- SimpleDelegateKernelInterface
このクラスは、委任されたパーティションを初期化/準備/実行するためのロジックをカプセル化します。
それは:(定義を参照)
- Init(...):1回限りの初期化を行うために1回呼び出されます。
- Prepare(...):このノードの異なるインスタンスごとに呼び出されます-これは、複数の委任されたパーティションがある場合に発生します。通常、ここでメモリ割り当てを行います。これは、テンソルのサイズが変更されるたびに呼び出されるためです。
- Invoke(...):推論のために呼び出されます。
例
この例では、float32テンソルのみで2種類の操作(ADD)と(SUB)のみをサポートできる非常に単純なデリゲートを作成します。
// MyDelegate implements the interface of SimpleDelegateInterface.
// This holds the Delegate capabilities.
class MyDelegate : public SimpleDelegateInterface {
public:
bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
const TfLiteNode* node,
TfLiteContext* context) const override {
// Only supports Add and Sub ops.
if (kTfLiteBuiltinAdd != registration->builtin_code &&
kTfLiteBuiltinSub != registration->builtin_code)
return false;
// This delegate only supports float32 types.
for (int i = 0; i < node->inputs->size; ++i) {
auto& tensor = context->tensors[node->inputs->data[i]];
if (tensor.type != kTfLiteFloat32) return false;
}
return true;
}
TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }
const char* Name() const override {
static constexpr char kName[] = "MyDelegate";
return kName;
}
std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
override {
return std::make_unique<MyDelegateKernel>();
}
};
次に、 SimpleDelegateKernelInterface
から継承して、独自のデリゲートカーネルを作成します
// My delegate kernel.
class MyDelegateKernel : public SimpleDelegateKernelInterface {
public:
TfLiteStatus Init(TfLiteContext* context,
const TfLiteDelegateParams* params) override {
// Save index to all nodes which are part of this delegate.
inputs_.resize(params->nodes_to_replace->size);
outputs_.resize(params->nodes_to_replace->size);
builtin_code_.resize(params->nodes_to_replace->size);
for (int i = 0; i < params->nodes_to_replace->size; ++i) {
const int node_index = params->nodes_to_replace->data[i];
// Get this node information.
TfLiteNode* delegated_node = nullptr;
TfLiteRegistration* delegated_node_registration = nullptr;
TF_LITE_ENSURE_EQ(
context,
context->GetNodeAndRegistration(context, node_index, &delegated_node,
&delegated_node_registration),
kTfLiteOk);
inputs_[i].push_back(delegated_node->inputs->data[0]);
inputs_[i].push_back(delegated_node->inputs->data[1]);
outputs_[i].push_back(delegated_node->outputs->data[0]);
builtin_code_[i] = delegated_node_registration->builtin_code;
}
return kTfLiteOk;
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) override {
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) override {
// Evaluate the delegated graph.
// Here we loop over all the delegated nodes.
// We know that all the nodes are either ADD or SUB operations and the
// number of nodes equals ''inputs_.size()'' and inputs[i] is a list of
// tensor indices for inputs to node ''i'', while outputs_[i] is the list of
// outputs for node
// ''i''. Note, that it is intentional we have simple implementation as this
// is for demonstration.
for (int i = 0; i < inputs_.size(); ++i) {
// Get the node input tensors.
// Add/Sub operation accepts 2 inputs.
auto& input_tensor_1 = context->tensors[inputs_[i][0]];
auto& input_tensor_2 = context->tensors[inputs_[i][1]];
auto& output_tensor = context->tensors[outputs_[i][0]];
TF_LITE_ENSURE_EQ(
context,
ComputeResult(context, builtin_code_[i], &input_tensor_1,
&input_tensor_2, &output_tensor),
kTfLiteOk);
}
return kTfLiteOk;
}
private:
// Computes the result of addition of 'input_tensor_1' and 'input_tensor_2'
// and store the result in 'output_tensor'.
TfLiteStatus ComputeResult(TfLiteContext* context, int builtin_code,
const TfLiteTensor* input_tensor_1,
const TfLiteTensor* input_tensor_2,
TfLiteTensor* output_tensor) {
if (NumElements(input_tensor_1) != NumElements(input_tensor_2) ||
NumElements(input_tensor_1) != NumElements(output_tensor)) {
return kTfLiteDelegateError;
}
// This code assumes no activation, and no broadcasting needed (both inputs
// have the same size).
auto* input_1 = GetTensorData<float>(input_tensor_1);
auto* input_2 = GetTensorData<float>(input_tensor_2);
auto* output = GetTensorData<float>(output_tensor);
for (int i = 0; i < NumElements(input_tensor_1); ++i) {
if (builtin_code == kTfLiteBuiltinAdd)
output[i] = input_1[i] + input_2[i];
else
output[i] = input_1[i] - input_2[i];
}
return kTfLiteOk;
}
// Holds the indices of the input/output tensors.
// inputs_[i] is list of all input tensors to node at index 'i'.
// outputs_[i] is list of all output tensors to node at index 'i'.
std::vector<std::vector<int>> inputs_, outputs_;
// Holds the builtin code of the ops.
// builtin_code_[i] is the type of node at index 'i'
std::vector<int> builtin_code_;
};
新しいデリゲートをベンチマークして評価する
TFLiteには、TFLiteモデルに対してすばやくテストできる一連のツールがあります。
- モデルベンチマークツール:このツールは、TFLiteモデルを取得し、ランダムな入力を生成してから、指定された回数だけモデルを繰り返し実行します。最後に、集約されたレイテンシ統計を出力します。
- 推論差分ツール:特定のモデルに対して、ツールはランダムなガウスデータを生成し、2つの異なるTFLiteインタープリターを通過させます。1つはシングルスレッドCPUカーネルを実行し、もう1つはユーザー定義の仕様を使用します。これは、要素ごとに、各インタープリターからの出力テンソル間の絶対差を測定します。このツールは、精度の問題のデバッグにも役立ちます。
- 画像分類とオブジェクト検出のためのタスク固有の評価ツールもあります。これらのツールはここにあります
さらに、TFLiteにはカーネルと運用ユニットテストの大規模なセットがあり、これらを再利用して、より多くのカバレッジで新しいデリゲートをテストし、通常のTFLite実行パスが壊れていないことを確認できます。
新しいデリゲートのTFLiteテストとツールの再利用を実現するには、次の2つのオプションのいずれかを使用できます。
- デリゲートレジストラメカニズムを利用します。
- 外部デリゲートメカニズムを利用します。
最良のアプローチの選択
どちらのアプローチでも、以下に詳述するようにいくつかの変更が必要です。ただし、最初のアプローチでは、デリゲートを静的にリンクし、テスト、ベンチマーク、および評価ツールを再構築する必要があります。対照的に、2つ目はデリゲートを共有ライブラリとして作成し、共有ライブラリからの作成/削除メソッドを公開する必要があります。
その結果、外部デリゲートメカニズムは、TFLiteの事前に構築されたTensorflowLiteツールバイナリで機能します。ただし、それはそれほど明確ではなく、自動統合テストで設定するのはより複雑になる可能性があります。わかりやすくするために、デリゲートレジストラアプローチを使用してください。
オプション1:デリゲートレジストラを活用する
デリゲートレジストラはデリゲートプロバイダーのリストを保持します。各プロバイダーは、コマンドラインフラグに基づいてTFLiteデリゲートを作成する簡単な方法を提供するため、ツールに便利です。上記のすべてのTensorflowLiteツールに新しいデリゲートをプラグインするには、最初にこのような新しいデリゲートプロバイダーを作成してから、BUILDルールにわずかな変更を加えるだけです。この統合プロセスの完全な例を以下に示します(コードはここにあります)。
以下に示すように、SimpleDelegate APIを実装するデリゲートと、この「ダミー」デリゲートを作成/削除する外部「C」APIがあると仮定します。
// Returns default options for DummyDelegate.
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault();
// Creates a new delegate instance that need to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options);
// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate);
「DummyDelegate」をベンチマークツールおよび推論ツールと統合するには、次のようにDelegateProviderを定義します。
class DummyDelegateProvider : public DelegateProvider {
public:
DummyDelegateProvider() {
default_params_.AddParam("use_dummy_delegate",
ToolParam::Create<bool>(false));
}
std::vector<Flag> CreateFlags(ToolParams* params) const final;
void LogParams(const ToolParams& params) const final;
TfLiteDelegatePtr CreateTfLiteDelegate(const ToolParams& params) const final;
std::string GetName() const final { return "DummyDelegate"; }
};
REGISTER_DELEGATE_PROVIDER(DummyDelegateProvider);
std::vector<Flag> DummyDelegateProvider::CreateFlags(ToolParams* params) const {
std::vector<Flag> flags = {CreateFlag<bool>("use_dummy_delegate", params,
"use the dummy delegate.")};
return flags;
}
void DummyDelegateProvider::LogParams(const ToolParams& params) const {
TFLITE_LOG(INFO) << "Use dummy test delegate : ["
<< params.Get<bool>("use_dummy_delegate") << "]";
}
TfLiteDelegatePtr DummyDelegateProvider::CreateTfLiteDelegate(
const ToolParams& params) const {
if (params.Get<bool>("use_dummy_delegate")) {
auto default_options = TfLiteDummyDelegateOptionsDefault();
return TfLiteDummyDelegateCreateUnique(&default_options);
}
return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}
ライブラリが常にリンクされており、オプティマイザによって削除されないようにする必要があるため、BUILDルールの定義は重要です。
#### The following are for using the dummy test delegate in TFLite tooling ####
cc_library(
name = "dummy_delegate_provider",
srcs = ["dummy_delegate_provider.cc"],
copts = tflite_copts(),
deps = [
":dummy_delegate",
"//tensorflow/lite/tools/delegates:delegate_provider_hdr",
],
alwayslink = 1, # This is required so the optimizer doesn't optimize the library away.
)
次に、これら2つのラッパールールをBUILDファイルに追加して、独自のデリゲートで実行できるベンチマークツールと推論ツールのバージョン、およびその他の評価ツールを作成します。
cc_binary(
name = "benchmark_model_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/benchmark:benchmark_model_main",
],
)
cc_binary(
name = "inference_diff_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/inference_diff:run_eval_lib",
],
)
cc_binary(
name = "imagenet_classification_eval_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/imagenet_image_classification:run_eval_lib",
],
)
cc_binary(
name = "coco_object_detection_eval_plus_dummy_delegate",
copts = tflite_copts(),
linkopts = task_linkopts(),
deps = [
":dummy_delegate_provider",
"//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
"//tensorflow/lite/tools/evaluation/tasks/coco_object_detection:run_eval_lib",
],
)
ここで説明するように、このデリゲートプロバイダーをTFLiteカーネルテストにプラグインすることもできます。
オプション2:外部デリゲートを活用する
この代替方法では、最初に、以下に示すように、外部デリゲートアダプタexternal_delegate_adaptor.ccを作成します。前述のように、このアプローチはオプション1と比較してわずかに好ましくないことに注意してください。
TfLiteDelegate* CreateDummyDelegateFromOptions(char** options_keys,
char** options_values,
size_t num_options) {
DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();
// Parse key-values options to DummyDelegateOptions.
// You can achieve this by mimicking them as command-line flags.
std::unique_ptr<const char*> argv =
std::unique_ptr<const char*>(new const char*[num_options + 1]);
constexpr char kDummyDelegateParsing[] = "dummy_delegate_parsing";
argv.get()[0] = kDummyDelegateParsing;
std::vector<std::string> option_args;
option_args.reserve(num_options);
for (int i = 0; i < num_options; ++i) {
option_args.emplace_back("--");
option_args.rbegin()->append(options_keys[i]);
option_args.rbegin()->push_back('=');
option_args.rbegin()->append(options_values[i]);
argv.get()[i + 1] = option_args.rbegin()->c_str();
}
// Define command-line flags.
// ...
std::vector<tflite::Flag> flag_list = {
tflite::Flag::CreateFlag(...),
...,
tflite::Flag::CreateFlag(...),
};
int argc = num_options + 1;
if (!tflite::Flags::Parse(&argc, argv.get(), flag_list)) {
return nullptr;
}
return TfLiteDummyDelegateCreate(&options);
}
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Defines two symbols that need to be exported to use the TFLite external
// delegate. See tensorflow/lite/delegates/external for details.
TFL_CAPI_EXPORT TfLiteDelegate* tflite_plugin_create_delegate(
char** options_keys, char** options_values, size_t num_options,
void (*report_error)(const char*)) {
return tflite::tools::CreateDummyDelegateFromOptions(
options_keys, options_values, num_options);
}
TFL_CAPI_EXPORT void tflite_plugin_destroy_delegate(TfLiteDelegate* delegate) {
TfLiteDummyDelegateDelete(delegate);
}
#ifdef __cplusplus
}
#endif // __cplusplus
次に、対応するBUILDターゲットを作成して、以下に示すようにダイナミックライブラリを構築します。
cc_binary(
name = "dummy_external_delegate.so",
srcs = [
"external_delegate_adaptor.cc",
],
linkshared = 1,
linkstatic = 1,
deps = [
":dummy_delegate",
"//tensorflow/lite/c:common",
"//tensorflow/lite/tools:command_line_flags",
"//tensorflow/lite/tools:logging",
],
)
この外部デリゲート.soファイルが作成された後、バイナリをビルドするか、ビルド済みのものを使用して、バイナリがここで説明するコマンドラインフラグをサポートするexternal_delegate_providerライブラリにリンクされている限り、新しいデリゲートで実行できます。注:この外部デリゲートプロバイダーは、既存のテストおよびツールバイナリにすでにリンクされています。
この外部デリゲートアプローチを介してダミーデリゲートをベンチマークする方法については、こちらの説明を参照してください。前述のテストおよび評価ツールにも同様のコマンドを使用できます。
ここに示すように、外部デリゲートはTensorflowLitePythonバインディングでのデリゲートの対応するC++実装であることに注意してください。したがって、ここで作成された動的外部デリゲートアダプタライブラリは、Tensorflow LitePythonAPIで直接使用できます。
資力
毎晩作成済みのTFLiteツールバイナリのリンクをダウンロードする
OS | アーチ | BINARY_NAME |
Linux | x86_64 | |
腕 | ||
aarch64 | ||
アンドロイド | 腕 | |
aarch64 |