Da die integrierte LiteRT-Operatorbibliothek nur eine begrenzte Anzahl von TensorFlow-Operatoren unterstützt, kann nicht jedes Modell konvertiert werden. Weitere Informationen finden Sie unter Kompatibilität mit Mobilfunkanbietern.
Damit die Konvertierung möglich ist, können Nutzer ihre eigene benutzerdefinierte Implementierung eines nicht unterstützten TensorFlow-Operators in LiteRT bereitstellen. Dies wird als benutzerdefinierter Operator bezeichnet. Wenn Sie stattdessen eine Reihe von nicht unterstützten (oder unterstützten) TensorFlow-Operatoren in einem einzelnen optimierten benutzerdefinierten Operator zusammenfassen möchten, lesen Sie den Abschnitt Operator-Fusing.
Die Verwendung benutzerdefinierter Operatoren umfasst vier Schritte.
TensorFlow-Modell erstellen Achten Sie darauf, dass sich das gespeicherte Modell (oder die Graph-Definition) auf den LiteRT-Operator mit dem richtigen Namen bezieht.
In ein LiteRT-Modell konvertieren: Achten Sie darauf, dass Sie das richtige LiteRT-Konverterattribut festlegen, damit das Modell erfolgreich konvertiert werden kann.
Operator erstellen und registrieren So weiß die LiteRT-Laufzeit, wie Ihr Operator und Ihre Parameter in Ihrem Diagramm ausführbarem C/C++-Code zugeordnet werden müssen.
Operator testen und profilieren Wenn Sie nur Ihren benutzerdefinierten Operator testen möchten, erstellen Sie am besten ein Modell, das nur Ihren benutzerdefinierten Operator enthält, und verwenden Sie das Programm benchmark_model.
Sehen wir uns ein End-to-End-Beispiel für die Ausführung eines Modells mit einem benutzerdefinierten Operator tf.atan
(mit dem Namen Atan
, siehe TensorFlow-Modell erstellen) an, der in TensorFlow, aber nicht in LiteRT unterstützt wird.
Der TensorFlow Text-Operator ist ein Beispiel für einen benutzerdefinierten Operator. Ein Codebeispiel finden Sie in der Anleitung zum Konvertieren von TF Text in LiteRT.
Beispiel: Benutzerdefinierter Atan
-Operator
Sehen wir uns ein Beispiel für die Unterstützung eines TensorFlow-Operators an, der in LiteRT nicht vorhanden ist. Angenommen, wir verwenden den Atan
-Operator und erstellen ein sehr einfaches Modell für eine Funktion y = atan(x + offset)
, wobei offset
trainierbar ist.
TensorFlow-Modell erstellen
Im folgenden Code-Snippet wird ein einfaches TensorFlow-Modell trainiert. Dieses Modell enthält nur einen benutzerdefinierten Operator namens Atan
, der eine Funktion y = atan(x +
offset)
ist, wobei offset
trainierbar ist.
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
Wenn Sie jetzt versuchen, ein LiteRT-Modell mit den Standard-Converter-Flags zu generieren, erhalten Sie die folgende Fehlermeldung:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
In ein LiteRT-Modell konvertieren
Erstellen Sie ein LiteRT-Modell mit benutzerdefinierten Operatoren, indem Sie das Konverterattribut allow_custom_ops
wie unten gezeigt festlegen:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Wenn Sie das Skript jetzt mit dem Standardinterpreter ausführen, z. B. mit den folgenden Befehlen:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Sie erhalten weiterhin die Fehlermeldung:
Encountered unresolved custom op: Atan.
Operator erstellen und registrieren
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
LiteRT-benutzerdefinierte Operatoren werden mit einer einfachen Pure-C-API definiert, die aus einem undurchsichtigen Typ (TfLiteOperator
) und zugehörigen Funktionen besteht.
TfLiteOperator
ist ein undurchsichtiger Typ:
typedef struct TfLiteOperator TfLiteOperator;
TfLiteOperator
speichert die Identität und Implementierung des Betreibers.
Der Operator unterscheidet sich von seinen Operanden, die in den LiteRT-Grafikknoten für Knoten gespeichert werden, die den Operator aufrufen.
Instanzen dieses Typs werden mit Aufrufen von TfLiteOperatorCreate
erstellt und können durch Aufrufen von TfLiteOperatorDelete
zerstört werden.
Die Identität des Operators wird über die Parameter der Konstruktorfunktion TfLiteOperatorCreate
festgelegt:
TfLiteOperator*
TfLiteOperatorCreate(
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.
);
In der Operatorimplementierung können „Methoden“ mit den folgenden Signaturen definiert werden.
Alle diese Methoden sind optional. Damit ein Operator erfolgreich ausgewertet werden kann, müssen in der Operatorimplementierung jedoch mindestens die Methoden Prepare
und Invoke
definiert und festgelegt werden (mit den Setter-Funktionen).
// 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);
Die Funktionsnamen names (oder Namespace-Präfixe für C++) in Ihrer Op-Implementierung müssen nicht mit den Funktionsnamen im obigen Code-Snippet übereinstimmen, da die TF Lite Custom Ops API nur ihre Adressen verwendet. Wir empfehlen sogar, sie in einem anonymen Namespace oder als statische Funktionen zu deklarieren.
Es empfiehlt sich jedoch, den Namen des Operators als Namespace oder Präfix in diese Funktionsnamen aufzunehmen:
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.
Da es sich um eine C-API handelt, werden diese „Methoden“ als C-Funktionszeiger im Typ TfLiteOperator
implementiert. Sie werden festgelegt, indem die Adressen Ihrer Implementierungsfunktionen an die entsprechenden Setter-Funktionen TfLiteOperatorSet
MethodName übergeben werden:
void TfLiteOperatorSetInit(
TfLiteOperator* operator,
void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
size_t length));
void TfLiteOperatorSetFree(
TfLiteOperator* operator,
void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
TfLiteOperator* operator,
TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
TfLiteOperator* operator,
TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
TfLiteOperator* operator,
struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
Weitere Informationen zu TfLiteContext
und TfLiteNode
finden Sie unter common.h
. TfLiteContext
bietet Funktionen zur Fehlerberichterstattung und Zugriff auf globale Objekte, einschließlich aller Tensoren.
Mit TfLiteNode
können Operatorimplementierungen auf ihre Ein- und Ausgaben zugreifen.
Wenn der Interpreter ein Modell lädt, ruft er die Methode Init()
einmal für jeden Knoten im Diagramm auf. Ein bestimmtes Init()
wird mehrmals aufgerufen, wenn der Vorgang mehrmals im Diagramm verwendet wird. Für benutzerdefinierte Vorgänge wird ein Konfigurationspuffer mit einem Flexbuffer bereitgestellt, der Parameternamen ihren Werten zuordnet. Der Puffer ist für integrierte Vorgänge leer, da der Interpreter die Vorgangsparameter bereits geparst hat. Kernelimplementierungen, die einen Status erfordern, sollten ihn hier initialisieren und die Inhaberschaft an den Aufrufer übertragen. Für jeden Init()
-Aufruf gibt es einen entsprechenden Aufruf von Free()
, sodass Implementierungen den Puffer freigeben können, den sie möglicherweise in Init()
zugewiesen haben.
Immer wenn die Größe der Eingabetensoren geändert wird, durchläuft der Interpreter den Graphen und benachrichtigt die Implementierungen über die Änderung. So haben sie die Möglichkeit, ihren internen Puffer anzupassen, die Gültigkeit von Eingabeformen und -typen zu prüfen und Ausgabefomen neu zu berechnen. Dies geschieht alles über die Methode Prepare()
. Implementierungen können mit TfLiteOpaqueNodeGetUserData(node)
auf ihren Status zugreifen.
Bei jeder Ausführung der Inferenz durchläuft der Interpreter den Graphen und ruft die Methode Invoke()
auf. Auch hier ist der Status als TfLiteOpaqueNodeGetUserData(node)
verfügbar.
Benutzerdefinierte Vorgänge können implementiert werden, indem diese „method“-Funktionen definiert werden und dann eine Funktion definiert wird, die eine Instanz von TfLiteOperator
zurückgibt, die durch Aufrufen von TfLiteOperatorCreate
und dann der relevanten Setter-Methoden erstellt wird:
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 TfLiteOperator* MyCustomOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* my_custom_op = ()[] { TfLiteOperator* r = TfLiteOperatorCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteOperatorSetInit(r, Init); TfLiteOperatorSetFree(r, Free); TfLiteOperatorSetPrepare(r, Prepare); TfLiteOperatorSetInvoke(r, Eval); return r; }; 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 TfLiteOperator* MyCustomOpCreate() { const TfLiteOperator* r = TfLiteOperatorCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteOperatorSetInit(r, MyCustomOpInit); TfLiteOperatorSetFree(r, MyCustomOpFree); TfLiteOperatorSetPrepare(r, MyCustomOpPrepare); TfLiteOperatorSetInvoke(r, MyCustomOpEval); return r; } const TfLiteOperator* MyCustomOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* my_custom_op = MyCustomOpCreate(); return my_custom_op; }
Die Registrierung erfolgt nicht automatisch. Es muss ein expliziter Aufruf Ihrer MyCustomOperator
-Funktion erfolgen (siehe Details unten). Während sich die Standard-BuiltinOpResolver
(verfügbar über das :builtin_ops
-Ziel) um die Registrierung von Built-ins kümmert, müssen benutzerdefinierte Vorgänge in separaten benutzerdefinierten Bibliotheken gesammelt werden.
Kernel in der LiteRT-Laufzeit definieren
Um den Vorgang in LiteRT zu verwenden, müssen wir nur zwei Funktionen (Prepare
und Eval
) und eine dritte zum Erstellen eines TfLiteOperator
definieren:
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 TfLiteOperator* AtanOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* atan_op = ()[] { auto* r = TfLiteOperatorCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteOperatorSetPrepare(r, Prepare); TfLiteOperatorSetInvoke(r, Eval); return r; }; 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 TfLiteOperator* AtanOpCreate() { TfLiteOperator* r = TfLiteOperatorCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteOperatorSetPrepare(r, Prepare); TfLiteOperatorSetInvoke(r, Eval); return r; } const TfLiteOperator* AtanOperator() { // Singleton instance, intentionally never destroyed. static const TfLiteOperator* atan_op = AtanOpCreate(); return atan_op; }
Fügen Sie beim Initialisieren von OpResolver
den benutzerdefinierten Vorgang in den Resolver ein (siehe Beispiel unten). Dadurch wird der Operator bei LiteRT registriert, sodass LiteRT die neue Implementierung verwenden kann.
Operator in der Kernel-Bibliothek registrieren
Jetzt müssen wir den Operator in der Kernel-Bibliothek registrieren. Dazu wird eine OpResolver
verwendet. Im Hintergrund lädt der Interpreter eine Bibliothek mit Kernels, die für die Ausführung der einzelnen Operatoren im Modell zugewiesen werden.
Die Standardbibliothek enthält nur integrierte Kerne. Sie kann jedoch durch eine benutzerdefinierte Bibliothek mit Operatoren ersetzt oder erweitert werden.
Die Klasse OpResolver
, die Operatorcodes und ‑namen in tatsächlichen Code übersetzt, ist so definiert:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Aus Gründen der Abwärtskompatibilität wird in dieser Klasse der ältere konkrete Typ TfLiteRegistration
anstelle des undurchsichtigen Typs TfLiteOperator
verwendet. Die TfLiteRegistration
-Struktur enthält jedoch ein registration_external
-Feld vom Typ TfLiteOperator*
.
Die Klassen MutableOpResolver
und BuiltinOpResolver
werden von OpResolver
abgeleitet:
class MutableOpResolver : public OpResolver {
public:
MutableOpResolver(); // Constructs an initially empty op resolver.
void AddAll(const MutableOpResolver& other);
...
};
class BuiltinOpResolver : public MutableOpResolver {
public:
BuiltinOpResolver(); // Constructs an op resolver with all the builtin ops.
};
Für die reguläre Verwendung (ohne benutzerdefinierte Vorgänge) müssen Sie BuiltinOpResolver
verwenden und Folgendes schreiben:
tflite::ops::builtin::BuiltinOpResolver resolver;
Um den oben erstellten benutzerdefinierten Vorgang hinzuzufügen, können Sie stattdessen ein MutableOpResolver
verwenden und tflite::AddOp
aufrufen (bevor Sie den Resolver an InterpreterBuilder
übergeben):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());
Wenn die Menge der integrierten Vorgänge als zu groß erachtet wird, kann ein neues OpResolver
basierend auf einer bestimmten Teilmenge von Vorgängen, möglicherweise nur den in einem bestimmten Modell enthaltenen, code-generiert werden. Dies entspricht der selektiven Registrierung von TensorFlow (eine einfache Version davon ist im Verzeichnis tools
verfügbar).
Wenn Sie Ihre benutzerdefinierten Operatoren in Java definieren möchten, müssen Sie derzeit eine eigene benutzerdefinierte JNI-Ebene erstellen und Ihr eigenes AAR in diesem JNI-Code kompilieren. Wenn Sie diese in Python verfügbaren Operatoren definieren möchten, können Sie Ihre Registrierungen im Python-Wrapper-Code platzieren.
Ein ähnlicher Prozess wie oben kann auch für die Unterstützung einer Reihe von Vorgängen anstelle eines einzelnen Operators verwendet werden. Fügen Sie einfach so viele AddCustom
-Operatoren hinzu, wie Sie benötigen. Außerdem können Sie mit MutableOpResolver
Implementierungen von Built-in-Funktionen mit AddBuiltin
überschreiben.
Operator testen und profilieren
Wenn Sie Ihren Vorgang mit dem LiteRT-Benchmark-Tool profilieren möchten, können Sie das Benchmark-Modell-Tool für LiteRT verwenden. Zu Testzwecken können Sie Ihren lokalen Build von LiteRT über Ihren benutzerdefinierten Vorgang informieren, indem Sie den entsprechenden AddCustom
-Aufruf (wie oben gezeigt) zu register.cc hinzufügen.
Best Practices
Arbeitsspeicherzuweisungen und ‑freigaben vorsichtig optimieren. Die Zuweisung von Arbeitsspeicher in
Prepare
ist effizienter als inInvoke
. Außerdem ist es besser, Arbeitsspeicher vor einer Schleife zuzuweisen als in jeder Iteration. Verwenden Sie temporäre Tensordaten anstatt selbst Speicher zuzuweisen (siehe Punkt 2). Verwenden Sie nach Möglichkeit Zeiger/Referenzen anstelle von Kopien.Wenn eine Datenstruktur während des gesamten Vorgangs bestehen bleibt, empfehlen wir, den Speicher mit temporären Tensoren vorab zuzuweisen. Möglicherweise müssen Sie eine OpData-Struktur verwenden, um in anderen Funktionen auf die Tensor-Indexe zu verweisen. Beispiel für den Kernel für die Faltung Unten finden Sie ein Beispielcode-Snippet.
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; }
Wenn nicht zu viel Speicher verschwendet wird, sollten Sie ein statisches Array mit fester Größe (oder ein vorab zugewiesenes
std::vector
inResize
) verwenden, anstatt bei jeder Ausführung ein dynamisch zugewiesenesstd::vector
zu verwenden.Vermeiden Sie das Instanziieren von Standardbibliotheks-Container-Vorlagen, die noch nicht vorhanden sind, da sie sich auf die Binärgröße auswirken. Wenn Sie beispielsweise eine
std::map
in Ihrem Vorgang benötigen, die in anderen Kernels nicht vorhanden ist, kann die Verwendung einerstd::vector
mit direkter Indexierungszuordnung funktionieren und gleichzeitig die Binärgröße gering halten. Sehen Sie sich an, was andere Kernels verwenden, um Erkenntnisse zu gewinnen (oder fragen Sie danach).Prüfen Sie den von
malloc
zurückgegebenen Zeiger auf den Speicher. Wenn dieser Zeigernullptr
ist, sollten keine Vorgänge mit diesem Zeiger ausgeführt werden. Wenn Siemalloc
in einer Funktion verwenden und einen Fehlerbeendigungszweig haben, geben Sie den Speicher frei, bevor Sie die Funktion beenden.Verwenden Sie
TF_LITE_OPAQUE_ENSURE(context, condition)
, um einen bestimmten Zustand zu prüfen. Ihr Code darf keinen Speicher zurücklassen, wennTF_LITE_OPAQUE_ENSURE
verwendet wird. Diese Makros sollten also verwendet werden, bevor Ressourcen zugewiesen werden, die verloren gehen.