LiteRT 組み込みオペレーター ライブラリは制限付きの すべてのモデルが変換可能とは限りません。詳しくは 演算子の互換性をご覧ください。
コンバージョンを可能にするために、ユーザーはカスタム実装を独自に提供できます。 LiteRT でサポートされていない TensorFlow 演算子(カスタム演算子と呼ばれます)。 代わりに、サポートされていない(またはサポートされている)一連の 単一の融合された最適化されたカスタム演算子への TensorFlow 演算子については、 演算子の融合
カスタム演算子の使用には、4 つのステップがあります。
TensorFlow モデルを作成する。[保存済み] が モデル(またはグラフ定義)は、正しい名前の LiteRT 演算子を指します。
LiteRT モデルに変換します。 以下の処理を行うためには、正しい LiteRT コンバータ属性を設定してください。 モデルを変換できます。
オペレーターを作成して登録します。この これにより、LiteRT ランタイムがオペレーターと 実行可能な C/C++ コードにグラフ内のパラメータを渡す必要があります。
事業者をテストしてプロファイリングします。もし カスタム オペレータだけをテストする場合は、 カスタム演算子のみを使用し benchmark_model 。
カスタムモデルを使用してモデルを実行するエンドツーエンドの例を
演算子 tf.atan
(名前は Atan
。TensorFlow モデルを作成するを参照)。
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
型は、次のアドレスを渡すことによって設定されます。
対応するセッター関数に実装関数を
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));
詳しくは、
common.h
TfLiteContext
と TfLiteNode
の詳細をご覧ください。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 つの関数を定義することだけです。
(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<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
の引数は AtanPrepare
と AtanEval
に対応します。
定義した値のみを使用できます。AtanInit
と AtanFree
を使用した場合
追加の関数を実行して、オペレーションで使用する変数を初期化し、領域を解放します。
場合、これらは最初の 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
ベスト プラクティス
メモリの割り当てと割り当て解除は慎重に最適化してください。メモリの割り当て
Prepare
はInvoke
よりも効率的であり、メモリを割り当てる より優れているということです。一時的なテンソルデータを使用する (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; }
無駄なメモリがあまり発生しない場合は、静的な固定サイズを使用することをおすすめします。 (または、
Resize
で事前に割り当てられたstd::vector
)を使用します。 実行の反復処理ごとにstd::vector
が動的に割り当てられます。まだ作成されていない標準のライブラリ コンテナ テンプレートはインスタンス化しないでください。 バイナリサイズに影響するからです。たとえば 他のカーネルには存在しない
std::map
をオペレーション内で使用し、 インデックスの直接マッピングを使用したstd::vector
は、 小さめのバイナリサイズにします。他のカーネルが何を使用して分析情報を得るか(または質問する)方法を確認してください。malloc
が返すメモリへのポインタを確認します。このポインタがnullptr
の場合、そのポインタを使用してオペレーションを実行しないでください。もし 関数内でmalloc
を実行し、エラーが発生した場合は、その前にメモリの割り当てを解除します。 終了します。TF_LITE_OPAQUE_ENSURE(context, condition)
を使用して、特定の値をチェックする あります。メモリがハングする状態になってはなりません。TF_LITE_OPAQUE_ENSURE
が使用されている。つまり、これらのマクロは 割り当てられてリークするリソースはありません