Da die integrierte LiteRT-Operator-Bibliothek nur eine nicht jedes Modell ist konvertierbar. Weitere Informationen Weitere Informationen zur Operatorkompatibilität
Um Conversions zu ermöglichen, können Nutzer ihre eigene benutzerdefinierte Implementierung eines nicht unterstützter TensorFlow-Operator in LiteRT (benutzerdefinierter Operator). Falls Sie stattdessen eine Reihe nicht unterstützter oder TensorFlow-Operatoren in einen einzelnen kombinierten, optimierten benutzerdefinierten Operator zusammengefasst, siehe Operator Fusion aus.
Die Verwendung benutzerdefinierter Operatoren umfasst vier Schritte.
Erstellen Sie ein TensorFlow-Modell. Stellen Sie sicher, dass die Model (oder Graph Def) bezieht sich auf den korrekt benannten LiteRT-Operator.
Konvertieren Sie es in ein LiteRT-Modell. Achten Sie darauf, dass Sie das richtige LiteRT-Conversion-Attribut angeben, um das Modell konvertiert.
Erstellen und registrieren Sie den Operator. Dieses damit die LiteRT-Laufzeit weiß, wie Ihr Operator und in ausführbaren C/C++-Code umwandeln.
Testen Sie Ihren Betreiber und erstellen Sie ein Profil. Wenn Sie nur Ihren benutzerdefinierten Operator testen möchten, erstellen Sie am besten ein Modell mit einfach den benutzerdefinierten Operator benchmark_model .
Sehen wir uns ein Beispiel für die Ausführung eines Modells mit einem benutzerdefinierten
Operator tf.atan
(namens Atan
, siehe TensorFlow-Modell erstellen), der
wird in TensorFlow unterstützt, in LiteRT jedoch nicht.
Der TensorFlow-Operator „Text“ ist ein Beispiel für einen benutzerdefinierten Operator. Weitere Informationen finden Sie in der Anleitung zum Konvertieren von TF Text in LiteRT mit einem Codebeispiel.
Beispiel: Benutzerdefinierter Atan
-Operator
Sehen wir uns ein Beispiel für die Unterstützung eines TensorFlow-Operators an, bei dem
LiteRT nicht. Angenommen, wir verwenden den Atan
-Operator
erstellen wir ein sehr einfaches Modell für die Funktion y = atan(x + offset)
, wobei
offset
ist trainierbar.
TensorFlow-Modell erstellen
Mit dem folgenden Code-Snippet wird ein einfaches TensorFlow-Modell trainiert. Dieses Modell hat
enthält einen benutzerdefinierten Operator mit dem Namen Atan
. Dies ist eine Funktion y = atan(x +
offset)
, bei der 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 versuchen, ein LiteRT-Modell mit der Standardeinstellung Konverter-Flags erhalten Sie die folgende Fehlermeldung:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
In ein LiteRT-Modell umwandeln
LiteRT-Modell mit benutzerdefinierten Operatoren durch Festlegen des Converters erstellen
Attribut allow_custom_ops
wie unten gezeigt:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Wenn Sie es mit dem Standard-Interpreter ausführen und Befehle wie folgt:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Die Fehlermeldung wird weiterhin angezeigt:
Encountered unresolved custom op: Atan.
Erstellen und registrieren Sie den Operator.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Benutzerdefinierte LiteRT-Operatoren werden mit einem einfachen, reinen C-API definiert,
besteht aus einem intransparenten Typ (TfLiteRegistrationExternal
) und verwandten Funktionen.
TfLiteRegistrationExternal
ist ein opaker Typ:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
speichert die Identität und Implementierung des Betreibers.
(Beachten Sie, dass sich der Operator von seinen Operanden unterscheidet, die im
LiteRT-Grafikknoten für Knoten, die den Operator aufrufen.)
Instanzen dieses Typs werden mit Aufrufen
TfLiteRegistrationExternalCreate
und kann durch den Aufruf gelöscht werden.
TfLiteRegistrationExternalDelete
.
Die Identität des Operators wird über die Parameter für die Konstruktorfunktion festgelegt.
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.
);
Die Operatorimplementierung kann „Methoden“ definieren mit den folgenden Signaturen.
Alle diese Methoden sind optional, aber
muss die Operatorimplementierung mithilfe des Setters
Funktionen) mindestens die Methoden Prepare
und 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);
die Namen der Funktion (oder Namespace-Präfixe für C++) in der Vorgangsimplementierung nicht mit den Funktionsnamen im obigen Code-Snippet übereinstimmen, Für die Lite Custom Operations API werden nur die Adressen verwendet. Wir empfehlen Ihnen sogar, Sie deklarieren sie in einem anonymen Namespace oder als statische Funktionen.
Es ist jedoch eine gute Idee, den Operatornamen als Namespace oder Präfix folgenden Funktionsnamen:
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 als C-Funktionszeiger in
den Typ TfLiteRegistrationExternal
, die festgelegt werden, indem die Adressen der
Ihre Implementierungsfunktionen mit den entsprechenden Setter-Funktionen
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));
Weitere Informationen finden Sie unter
common.h
findest du weitere Informationen zu TfLiteContext
und TfLiteNode
. TfLiteContext
gibt einen Fehler zurück
und Zugriff auf
globale Objekte, einschließlich aller Tensoren.
TfLiteNode
ermöglicht Operatorimplementierungen den Zugriff auf ihre Ein- und Ausgaben.
Wenn der Interpreter ein Modell lädt, wird die Methode Init()
jeweils einmal aufgerufen.
Knoten im Diagramm. Ein bestimmtes Init()
wird mehr als einmal aufgerufen, wenn der Vorgang
in der Grafik mehrfach verwendet wird. Bei benutzerdefinierten Vorgängen ist ein Konfigurationspuffer
bereitgestellt, die einen Flex-Zwischenspeicher enthält, der Parameternamen ihren Werten zuordnet. Die
Für integrierte Operationen ist der Zwischenspeicher leer, da der Interpreter den
op-Parameter. Kernelimplementierungen, die einen Status erfordern, sollten ihn initialisieren
und die Eigentümerschaft an den Anrufer übertragen. Für jeden Init()
-Aufruf werden
einen entsprechenden Aufruf von Free()
, mit dem Implementierungen die
Zwischenspeicher, den er möglicherweise in Init()
zugewiesen hat.
Immer wenn die Größe der Eingabetensoren geändert wird, durchläuft der Interpreter die
Diagramm, das Implementierungen der Änderung informiert. Das gibt ihnen die Möglichkeit,
die Größe des internen Zwischenspeichers ändern, die Gültigkeit von Eingabeformen und -typen überprüfen und
die Ausgabeformen neu zu berechnen. Dies erfolgt alles über die Methode Prepare()
.
Implementierungen auf ihren Status zugreifen können,
TfLiteOpaqueNodeGetUserData(node)
.
Jedes Mal, wenn eine Inferenz ausgeführt wird, durchläuft der Interpreter schließlich den Graph,
Methode Invoke()
. Auch hier ist der Status als
TfLiteOpaqueNodeGetUserData(node)
.
Benutzerdefinierte Vorgänge können implementiert werden, indem diese "Methode" definiert wird. Funktionen und dann
Definieren einer Funktion, die eine Instanz von TfLiteRegistrationExternal
zurückgibt
konstruiert, indem TfLiteRegistrationExternalCreate
und dann die entsprechende
Setter-Methoden:
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; }
Die Registrierung erfolgt nicht automatisch und ist ein expliziter Aufruf an Ihre
MyCustomOpRegistration
-Funktion festgelegt werden (siehe Details unten). Während die
Standard-BuiltinOpResolver
(verfügbar über das Ziel :builtin_ops
) verwendet
Registrierung von builtins, benutzerdefinierte Operationen müssen in
benutzerdefinierte Bibliotheken.
Kernel in der LiteRT-Laufzeit definieren
Um den op in LiteRT zu verwenden, müssen wir nur zwei Funktionen definieren.
(Prepare
und Eval
) und ein drittes zum Erstellen von 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; }
Fügen Sie beim Initialisieren von OpResolver
den benutzerdefinierten Vorgang in den Resolver (siehe
unten ein Beispiel). Dadurch wird der Betreiber bei LiteRT registriert,
damit LiteRT die neue Implementierung verwenden kann. Beachten Sie, dass die letzten beiden
Argumente in TfLiteRegistration
entsprechen AtanPrepare
und AtanEval
die Sie für den benutzerdefinierten Vorgang definiert haben. Wenn Sie AtanInit
und AtanFree
verwendet haben
zum Initialisieren von Variablen, die im Vorgang verwendet werden, und um Speicherplatz freizugeben,
werden sie den ersten beiden Argumenten
TfLiteRegistration
; Diese Argumente sind in diesem Beispiel auf nullptr
gesetzt.
Operator bei der Kernel-Bibliothek registrieren
Jetzt müssen wir den Operator bei der Kernel-Bibliothek registrieren. Dies geschieht mit
ein OpResolver
. Im Hintergrund lädt der Dolmetscher eine Bibliothek mit
Kernels, die für die Ausführung der einzelnen Operatoren im Modell zugewiesen werden.
Die Standardbibliothek enthält zwar nur integrierte Kernel, es ist jedoch möglich,
durch benutzerdefinierte Bibliotheksoperatoren ersetzen/erweitern.
Die Klasse OpResolver
, die Operatorcodes und -namen in tatsächliche
wie folgt definiert:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Beachten Sie, dass diese Klasse aus Gründen der Abwärtskompatibilität den älteren konkreten Typ verwendet.
TfLiteRegistration
statt TfLiteRegistrationExternal
des Typs „undurchsichtig“,
Die Struktur TfLiteRegistration
enthält jedoch ein registration_external
-Feld mit
Geben Sie TfLiteRegistrationExternal*
ein.
Die Klassen MutableOpResolver
und BuiltinOpResolver
werden abgeleitet aus
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.
};
Für die normale Verwendung (ohne benutzerdefinierte Vorgänge) muss die BuiltinOpResolver
verwendet werden.
und schreibe:
tflite::ops::builtin::BuiltinOpResolver resolver;
Sie können stattdessen MutableOpResolver
verwenden, um die oben erstellte benutzerdefinierte Operation hinzuzufügen.
und rufe AddCustom
auf (bevor du den Resolver an den
InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Wenn die Gruppe von integrierten Vorgängen als zu groß erachtet wird, könnte ein neuer OpResolver
Code basierend auf einer bestimmten Teilmenge von Operationen generiert, möglicherweise nur
in einem Modell an. 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 Erstellen Sie Ihre eigene JNI-Schicht und kompilieren Sie eigene AAE. in diesem Jni-Code. Wenn Sie diese in Python verfügbaren Operatoren definieren möchten, können Sie registrieren Sie sich im Python-Wrapper-Code
Ein ähnlicher Prozess wie oben kann verwendet werden, um eine Reihe von
Operationen statt mit einem einzelnen Operator. Fügen Sie einfach so viele AddCustom
-Operatoren hinzu
je nach Bedarf anpassen. Außerdem können Sie mit MutableOpResolver
Implementierungen von builtins mithilfe von AddBuiltin
.
Operator testen und Profil erstellen
Um mit dem LiteRT-Benchmark-Tool ein Profil für Ihre Operation zu erstellen, können Sie das
Benchmark-Modell-Tool
für LiteRT. Zu Testzwecken können Sie Ihren lokalen Build aus
LiteRT kennt Ihre benutzerdefinierte Operation durch Hinzufügen des entsprechenden AddCustom
anrufen (wie oben gezeigt) bei
register.cc
Best Practices
Optimieren Sie Arbeitsspeicherzuweisungen und ‐zuweisungen vorsichtig. Arbeitsspeicher wird zugewiesen in
Prepare
ist effizienter als inInvoke
und es wird Arbeitsspeicher zugewiesen vor einer Schleife besser ist als in jeder Iteration. Temporäre Tensor-Daten verwenden statt sich selbst zu verkaufen (siehe Punkt 2). Stattdessen Verweise/Verweise verwenden so viel wie möglich zu kopieren.Wenn eine Datenstruktur während des gesamten Vorgangs bestehen bleibt, empfehlen wir, Vorabzuweisung des Arbeitsspeichers mithilfe temporärer Tensoren. Möglicherweise müssen Sie ein OpData-Struktur, um auf die Tensor-Indizes in anderen Funktionen zu verweisen. Weitere Informationen finden Sie in der Beispiel im Kernel for Convolution Im Folgenden finden Sie ein Beispiel für ein Code-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 dies nicht zu viel vergeudeten Arbeitsspeicher verursacht, sollten Sie lieber eine statische feste Größe verwenden. Array (oder eine vorab zugewiesene
std::vector
inResize
) anstelle einesstd::vector
wird bei jeder Ausführungsdurchlauf dynamisch zugewiesen.Instanziieren von Containervorlagen für Standardbibliotheken, die noch nicht geschehen sind, vermeiden da sie sich auf die Binärgröße auswirken. Wenn Sie zum Beispiel eine
std::map
in Ihrem Vorgang, der in anderen Kerneln nicht vorhanden ist, mit einemstd::vector
mit direkter Indexierungszuordnung könnte funktionieren, während der Binärgröße klein ist. Sehen Sie sich an, was andere Kernel verwenden, um Erkenntnisse zu gewinnen (oder fragen Sie nach).Prüfen Sie den Zeiger auf den von
malloc
zurückgegebenen Arbeitsspeicher. Wenn dieser Zeigernullptr
, sollten keine Vorgänge mit diesem Zeiger ausgeführt werden. Wenn Siemalloc
in einer Funktion und haben einen Fehler-Exit. Weisen Sie Arbeitsspeicher auf, bevor Sie beenden.Mit
TF_LITE_OPAQUE_ENSURE(context, condition)
können Sie nach einem bestimmten . Ihr Code darf die Erinnerung nicht hängen lassen, wennTF_LITE_OPAQUE_ENSURE
wird verwendet, d.h. diese Makros sollten vor dem alle Ressourcen zugewiesen sind, die ein Datenleck verursachen.