カスタム演算子

LiteRT 組み込みオペレーター ライブラリは制限付きの すべてのモデルが変換可能とは限りません。詳しくは 演算子の互換性をご覧ください。

コンバージョンを可能にするために、ユーザーはカスタム実装を独自に提供できます。 LiteRT でサポートされていない TensorFlow 演算子(カスタム演算子と呼ばれます)。 代わりに、サポートされていない(またはサポートされている)一連の 単一の融合された最適化されたカスタム演算子への TensorFlow 演算子については、 演算子の融合

カスタム演算子の使用には、4 つのステップがあります。

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

TensorFlow Text 演算子はカスタム演算子の一例です。詳しくは、 コード例については、TF テキストを LiteRT に変換するのチュートリアルをご覧ください。

例: カスタム Atan 演算子

TensorFlow 演算子をサポートする例を見ていきましょう。 LiteRT はそうではありません。Atan 演算子を使用しているとします。 関数 y = atan(x + offset) の非常にシンプルなモデルを構築します。 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

この時点で、デフォルト構成で LiteRT モデルを生成しようとすると、 使用すると、次のエラー メッセージが表示されます。

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

LiteRT モデルに変換する

コンバータを設定して、カスタム オペレータを使用して LiteRT モデルを作成する 次のように allow_custom_ops 属性を指定します。

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"

LiteRT カスタム オペレータは、単純なピュア C API を使用して定義されます。 不透明型(TfLiteRegistrationExternal)と関連する関数で構成されます。

TfLiteRegistrationExternal は不透明型です。

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

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

この型のインスタンスは、 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.
);

演算子の実装で「メソッド」を定義できます。次のシグネチャを使用します。 これらのメソッドはすべて省略可能ですが、 演算子の実装で(setter を使用して)を定義し、 少なくとも 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);

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

ただし、演算子名を名前空間または接頭辞として 次の関数名:

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 であるため、これらの「メソッド」はC 関数ポインタとして実装され、 TfLiteRegistrationExternal 型は、次のアドレスを渡すことによって設定されます。 対応するセッター関数に実装関数を 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));

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

インタープリタはモデルを読み込むときに、モデルを読み込むたびに Init() メソッドを呼び出します。 表示されます。特定の Init() は、オペレーションが複数回呼び出されると複数回呼び出されます グラフで複数回使用されています。カスタム オペレーションの場合、構成バッファは 指定され、パラメータ名をその値にマッピングするフレックスバッファが含まれています。「 インタプリタがすでに変数のパースを完了しているため、 op パラメータを使用します。状態を必要とするカーネル実装は状態を初期化する必要がある オーナーにオーナー権限を譲渡します。Init() の呼び出しごとに、 対応する Free() の呼び出し。実装によって、 Init() に割り当てられた可能性のあるバッファです。

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

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

カスタム オペレーションは、これらの「メソッド」を定義することで実装できます。関数を選択してから、 TfLiteRegistrationExternal のインスタンスを返す関数を定義する これは、TfLiteRegistrationExternalCreate を呼び出してから関連する関数を セッター メソッド:

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 ターゲットから使用可能)は、 カスタム オペレーションを Google Cloud コンソールで収集し、 カスタム ライブラリを作成できます。

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

LiteRT で演算を使用するために必要なのは、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<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(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<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(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 を初期化するときに、リゾルバにカスタム演算を追加します( 下で例をご覧ください)。これによりオペレーターが LiteRT に登録され、 新しい実装を使用できることを確認します。最後の 2 つは TfLiteRegistration の引数は AtanPrepareAtanEval に対応します。 定義した値のみを使用できます。AtanInitAtanFree を使用した場合 追加の関数を実行して、オペレーションで使用する変数を初期化し、領域を解放します。 場合、これらは最初の 2 つの引数に TfLiteRegistration、この例では、これらの引数は 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 構造体には、次の registration_external フィールドが含まれています。 タイプ TfLiteRegistrationExternal*

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

組み込み演算のセットが大きすぎるとみなされた場合、新しい OpResolver が発生する可能性があります。 含まれるもののみに基づいて、特定の op のサブセットに基づいて 学習しますこれは TensorFlow の選択的登録と同等です。 (シンプルなバージョンは tools ディレクトリにあります)。

Java でカスタム演算子を定義する場合は、現在、 独自のカスタム JNI レイヤの構築と AAR のコンパイル こちらの jni コードをご覧ください。 同様に、Python で使用できるこれらの演算子を定義する場合は、 登録を Python ラッパーコード

一連の API をサポートする場合も、上記と同様のプロセスを使用できます。 複数のオペレーションを実行できます。AddCustom 演算子を必要なだけ追加します。 できます。また、MutableOpResolver を使用すると、リクエスト内の AddBuiltin を使用した組み込みの実装。

事業者のテストとプロファイリング

LiteRT ベンチマーク ツールで演算をプロファイリングするには、 ベンチマーク モデルツール 。テスト用に、ローカルビルドをビルドし、 適切な AddCustom を追加することで、LiteRT がカスタム演算を認識する (上図のように) register.cc

ベスト プラクティス

  1. メモリの割り当てと割り当て解除は慎重に最適化してください。メモリの割り当て PrepareInvoke よりも効率的であり、メモリを割り当てる より優れているということです。一時的なテンソルデータを使用する (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. 無駄なメモリがあまり発生しない場合は、静的な固定サイズを使用することをおすすめします。 (または、Resize で事前に割り当てられた std::vector)を使用します。 実行の反復処理ごとに std::vector が動的に割り当てられます。

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

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

  6. TF_LITE_OPAQUE_ENSURE(context, condition) を使用して、特定の値をチェックする あります。メモリがハングする状態になってはなりません。 TF_LITE_OPAQUE_ENSURE が使用されている。つまり、これらのマクロは 割り当てられてリークするリソースはありません