TensorFlow Lite の組み込み演算子ライブラリでサポートされる TensorFlow 演算子の数は限られているため、すべてのモデルが変換可能であるわけではありません。詳しくは、演算子の互換性をご覧ください。
変換を可能にするために、ユーザーは TensorFlow Lite でサポートされていない TensorFlow 演算子(カスタム オペレータ)の独自のカスタム実装を提供できます。サポートされていない(またはサポートされている)一連の TensorFlow 演算子を 1 つの融合された最適化されたカスタム演算子に結合する場合は、演算子の融合をご覧ください。
カスタム オペレータの使用は、4 つのステップで構成されます。
TensorFlow モデルを作成する。保存済みモデル(またはグラフ定義)が、正しい名前の TensorFlow Lite 演算子を参照していることを確認します。
TensorFlow Lite モデルに変換します。モデルを正常に変換するために、適切な TensorFlow Lite コンバータ属性を設定していることを確認してください。
演算子を作成して登録します。これは、TensorFlow Lite ランタイムがグラフ内の演算子とパラメータを実行可能な C/C++ コードにマッピングする方法を認識するためです。
オペレーターをテストしてプロファイリングします。カスタム オペレータだけをテストする場合は、カスタム オペレータだけを使用してモデルを作成し、benchmark_model プログラムを使用することをおすすめします。
カスタム演算子 tf.atan
(Atan
という名前。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 関数ポインタとして実装されます。これは、実装関数のアドレスを対応するセッター関数 TfLiteRegistrationExternalSet
MethodName に渡すことで設定されます。
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));
TfLiteContext
と TfLiteNode
の詳細については、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 つの関数(Prepare
と Eval
)を定義することと、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 のローカルビルドがカスタム演算を認識するようにできます。
ベスト プラクティス
メモリ割り当てと割り当て解除は慎重に最適化してください。
Prepare
でメモリを割り当てるほうがInvoke
よりも効率的です。また、反復処理ごとにメモリを割り当てるよりも、ループの前にメモリを割り当てるほうが効果的です。自分自身を malloc する代わりに、一時的なテンソルデータを使用します(項目 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; }
メモリの無駄遣いが多くない場合は、実行の反復処理ごとに動的に割り当てられる
std::vector
を使用するよりも、静的な固定サイズ配列(またはResize
で事前に割り当てられたstd::vector
)を使用することをおすすめします。バイナリサイズに影響するため、存在しない標準のライブラリ コンテナ テンプレートはインスタンス化しないでください。たとえば、他のカーネルに存在しない
std::map
がオペレーションに必要な場合、直接インデックス マッピングでstd::vector
を使用すると、バイナリサイズを小さく保ちながら機能します。他のカーネルが何を使用してインサイトを得るかを確認する(または質問する)。malloc
から返されたメモリへのポインタを確認します。このポインタがnullptr
の場合、そのポインタを使用してオペレーションは実行されません。関数内でmalloc
を実行したときにエラーが発生した場合は、終了する前にメモリの割り当てを解除します。TF_LITE_OPAQUE_ENSURE(context, condition)
を使用して、特定の条件を確認します。TF_LITE_OPAQUE_ENSURE
の使用時に、コードでメモリがハングアップしないようにする必要があります。つまり、リークするリソースを割り当てる前に、これらのマクロを使用する必要があります。