カスタム演算子

TensorFlow Lite の組み込み演算子ライブラリでサポートされる TensorFlow 演算子の数は限られているため、すべてのモデルが変換可能であるわけではありません。詳しくは、演算子の互換性をご覧ください。

変換を可能にするために、ユーザーは TensorFlow Lite でサポートされていない TensorFlow 演算子(カスタム オペレータ)の独自のカスタム実装を提供できます。サポートされていない(またはサポートされている)一連の TensorFlow 演算子を 1 つの融合された最適化されたカスタム演算子に結合する場合は、演算子の融合をご覧ください。

カスタム オペレータの使用は、4 つのステップで構成されます。

カスタム演算子 tf.atanAtan という名前。TensorFlow モデルの作成を参照)を使用してモデルを実行するエンドツーエンドの例を見てみましょう。これは TensorFlow ではサポートされていますが、TensorFlow Lite ではサポートされていません。

カスタム演算子の例として、TensorFlow のテキスト演算子があります。コード例については、TF テキストを TF Lite に変換するのチュートリアルをご覧ください。

例: カスタム Atan 演算子

TensorFlow Lite ではサポートされていない TensorFlow 演算子をサポートする例を見ていきましょう。Atan 演算子を使用して、offset がトレーニング可能な関数 y = atan(x + offset) の非常にシンプルなモデルを作成しているとします。

TensorFlow モデルを作成する

次のコード スニペットは、単純な TensorFlow モデルをトレーニングします。このモデルには、Atan という名前のカスタム オペレータが含まれています。これは関数 y = atan(x + offset) で、offset はトレーニング可能です。

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

この時点で、デフォルトのコンバータ フラグを使用して TensorFlow Lite モデルを生成しようとすると、次のエラー メッセージが表示されます。

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

TensorFlow Lite モデルに変換する

コンバータ属性 allow_custom_ops を以下のように設定して、カスタム演算子を使用して TensorFlow Lite モデルを作成します。

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

この時点で、次のようなコマンドを使用してデフォルトのインタープリタで実行すると、

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

引き続き次のようなエラーが表示されます。

Encountered unresolved custom op: Atan.

オペレーターを作成して登録します。

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

TensorFlow Lite カスタム演算子は、不透明型(TfLiteRegistrationExternal)と関連関数で構成される単純な純粋な C API を使用して定義されます。

TfLiteRegistrationExternal は不透明型です。

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal にはオペレーターの ID と実装が格納されます。(この演算子は、演算子を呼び出すノードの TF Lite グラフノードに格納されるオペランドとは異なります)。

このタイプのインスタンスは TfLiteRegistrationExternalCreate の呼び出しで構築され、TfLiteRegistrationExternalDelete を呼び出して破棄できます。

演算子の ID は、パラメータを介してコンストラクタ関数 TfLiteRegistrationExternalCreate に設定されます。

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    TfLiteBuiltinOperator builtin_code,  // Normally `TfLiteBuiltinCustom`.
    const char* custom_name,  // The name of the custom op.
    int version  // Normally `1` for the first version of a custom op.
);

演算子の実装では、次のシグネチャを使用して「メソッド」を定義できます。これらのメソッドはすべて省略可能ですが、演算子を適切に評価するには、演算子の実装で少なくとも Prepare メソッドと Invoke メソッドを定義して(セッター関数を使用して)設定する必要があります。

// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);

// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);

// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
                              TfLiteOpaqueNode* node);

TF Lite カスタム オペレーション API はアドレスのみを使用するため、演算実装の関数名names(C++ の場合は名前空間接頭辞)が上記のコード スニペットの関数名と一致している必要はありません。匿名の名前空間または静的な関数として宣言することをおすすめします。

ただし、演算子名を名前空間または接頭辞として関数名に含めることをおすすめします。

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

C

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

これは C API であるため、これらの「メソッド」は TfLiteRegistrationExternal 型の C 関数ポインタとして実装されます。これは、実装関数のアドレスを対応するセッター関数 TfLiteRegistrationExternalSetMethodName に渡すことで設定されます。

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

TfLiteContextTfLiteNode の詳細については、common.h をご覧ください。TfLiteContext はエラー報告機能と、すべてのテンソルを含むグローバル オブジェクトへのアクセスを提供します。TfLiteNode を使用すると、オペレーターの実装は入力と出力にアクセスできます。

インタープリタはモデルを読み込むときに、グラフ内のノードごとに Init() メソッドを 1 回呼び出します。グラフ内で演算が複数回使用されると、特定の Init() が複数回呼び出されます。カスタム オペレーションでは、パラメータ名を値にマッピングするフレックス バッファを含む構成バッファが提供されます。インタープリタがすでに op パラメータを解析しているため、組み込み op のバッファは空です。状態を必要とするカーネル実装は、ここで初期化し、所有権を呼び出し元に移行する必要があります。Init() 呼び出しごとに、対応する Free() の呼び出しがあります。これにより、実装は Init() に割り当てたバッファを破棄できます。

入力テンソルのサイズが変更されるたびに、インタープリタは変更の実装を通知するグラフを調べます。これにより、内部バッファのサイズ変更、入力の形状と型の有効性の確認、出力の形状の再計算が可能になります。これはすべて Prepare() メソッドを介して行われ、実装は TfLiteOpaqueNodeGetUserData(node) を使用して状態にアクセスできます。

最後に、推論が実行されるたびに、インタープリタは Invoke() メソッドを呼び出してグラフを走査します。ここでも状態は TfLiteOpaqueNodeGetUserData(node) として利用できます。

カスタム演算を実装するには、これらの「メソッド」関数を定義し、TfLiteRegistrationExternalCreate と関連するセッター メソッドを呼び出して作成された TfLiteRegistrationExternal のインスタンスを返す関数を定義します。

C++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

C

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

登録は自動的に行われないため、MyCustomOpRegistration 関数を明示的に呼び出す必要があります(詳細は下記を参照)。標準の BuiltinOpResolver:builtin_ops ターゲットから入手可能)が組み込みの登録を処理しますが、カスタム オペレーションは別のカスタム ライブラリに収集する必要があります。

TensorFlow Lite ランタイムでのカーネルの定義

TensorFlow Lite で演算を使用するために必要なことは、2 つの関数(PrepareEval)を定義することと、3 つ目の関数で TfLiteRegistrationExternal を作成することだけです。

C++

namespace atan_op {
  namespace {
    TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

      TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
      for (int i=0; i < num_dims; ++i) {
        output_size->data[i] = input->dims->data[i];
      }

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

    TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      float* input_data = static_cast(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast(TfLiteOpaqueTensorData(output));

      size_t count = 1;
      int num_dims = TfLiteOpaqueTensorNumDimensions(input);
      for (int i = 0; i < num_dims; ++i) {
        count *= input->dims->data[i];
      }

      for (size_t i = 0; i < count; ++i) {
        output_data[i] = atan(input_data[i]);
      }
      return kTfLiteOk;
    }
  }  // anonymous namespace

  const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

C

static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  float* input_data = static_cast(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast(TfLiteOpaqueTensorData(output));

  size_t count = 1;
  int num_dims = TfLiteOpaqueTensorNumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i = 0; i < count; ++i) {
    output_data[i] = atan(input_data[i]);
  }
  return kTfLiteOk;
}

static const TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

OpResolver を初期化するときに、カスタム演算をリゾルバに追加します(以下の例をご覧ください)。これにより演算子が Tensorflow Lite に登録され、TensorFlow Lite で新しい実装を使用できるようになります。TfLiteRegistration の最後の 2 つの引数は、カスタム演算に定義した AtanPrepare 関数と AtanEval 関数に対応しています。AtanInit 関数と AtanFree 関数を使用して演算で使用する変数を初期化し、スペースを解放すると、TfLiteRegistration の最初の 2 つの引数に追加されます。この例では、これらの引数は nullptr に設定されています。

カーネル ライブラリにオペレーターを登録する

次に、演算子をカーネル ライブラリに登録する必要があります。これは OpResolver で行います。その背後で、インタープリタがカーネルのライブラリを読み込みます。カーネルは、モデル内の各演算子を実行するために割り当てられます。デフォルトのライブラリには組み込みカーネルのみが含まれていますが、カスタム ライブラリ演算演算子に置き換えるか拡張できます。

演算子のコードと名前を実際のコードに変換する OpResolver クラスは次のように定義されます。

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

下位互換性を確保するため、このクラスでは不透明型 TfLiteRegistrationExternal ではなく古い具象型 TfLiteRegistration が使用されますが、TfLiteRegistration 構造体には TfLiteRegistrationExternal* 型の registration_external フィールドが含まれています。

MutableOpResolver クラスと BuiltinOpResolver クラスは OpResolver から派生しています。

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

通常の使用(カスタム オペレーションなし)では、BuiltinOpResolver を使用して以下を記述する必要があります。

tflite::ops::builtin::BuiltinOpResolver resolver;

上記で作成したカスタム演算を追加するには、代わりに MutableOpResolver を使用し、AddCustom を呼び出します(リゾルバを InterpreterBuilder に渡す前に)。

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

組み込み op のセットが大きすぎると判断された場合は、op の特定のサブセット(場合によっては特定のモデルに含まれるもののみ)に基づいて、新しい OpResolver をコード生成できます。これは、TensorFlow の選択的登録に相当します(単純なバージョンは tools ディレクトリにあります)。

Java でカスタム演算子を定義する場合は、現在、この jni コード内で独自のカスタム JNI レイヤを構築し、独自の AAR をコンパイルする必要があります。同様に、Python で使用できる演算子を定義する場合は、Python ラッパーコードに登録を配置できます。

上と同様のプロセスに従って、単一の演算子の代わりに一連の演算をサポートできます。AddCustom 演算子を必要な数だけ追加します。さらに、MutableOpResolver では、AddBuiltin を使用して組み込みの実装をオーバーライドすることもできます。

オペレーターのテストとプロファイリングを行う

TensorFlow Lite ベンチマーク ツールを使用して演算をプロファイリングするには、TensorFlow Lite のベンチマーク モデルツールを使用します。テスト目的で、適切な AddCustom 呼び出し(上記を参照)を register.cc に追加することで、TensorFlow Lite のローカルビルドがカスタム演算を認識するようにできます。

ベスト プラクティス

  1. メモリ割り当てと割り当て解除は慎重に最適化してください。Prepare でメモリを割り当てるほうが Invoke よりも効率的です。また、反復処理ごとにメモリを割り当てるよりも、ループの前にメモリを割り当てるほうが効果的です。自分自身を malloc する代わりに、一時的なテンソルデータを使用します(項目 2 を参照)。可能な限りコピーするのではなく、ポインタや参照を使用します。

  2. オペレーション全体を通じてデータ構造が維持される場合は、一時的なテンソルを使用してメモリを事前に割り当てることをおすすめします。他の関数のテンソル インデックスを参照するために、OpData 構造体の使用が必要になる場合があります。畳み込みカーネルの例をご覧ください。サンプルコード スニペットを以下に示します。

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. メモリの無駄遣いが多くない場合は、実行の反復処理ごとに動的に割り当てられる std::vector を使用するよりも、静的な固定サイズ配列(または Resize で事前に割り当てられた std::vector)を使用することをおすすめします。

  4. バイナリサイズに影響するため、存在しない標準のライブラリ コンテナ テンプレートはインスタンス化しないでください。たとえば、他のカーネルに存在しない std::map がオペレーションに必要な場合、直接インデックス マッピングで std::vector を使用すると、バイナリサイズを小さく保ちながら機能します。他のカーネルが何を使用してインサイトを得るかを確認する(または質問する)。

  5. malloc から返されたメモリへのポインタを確認します。このポインタが nullptr の場合、そのポインタを使用してオペレーションは実行されません。関数内で malloc を実行したときにエラーが発生した場合は、終了する前にメモリの割り当てを解除します。

  6. TF_LITE_OPAQUE_ENSURE(context, condition) を使用して、特定の条件を確認します。TF_LITE_OPAQUE_ENSURE の使用時に、コードでメモリがハングアップしないようにする必要があります。つまり、リークするリソースを割り当てる前に、これらのマクロを使用する必要があります。