Wbudowana biblioteka operatorów LiteRT obsługuje tylko liczba operatorów TensorFlow, nie każdy model można konwertować. Więcej informacji: znajdziesz w artykule o zgodności operatorów.
Aby umożliwić konwersję, użytkownicy mogą wprowadzić własną, niestandardową implementację nieobsługiwany operator TensorFlow w LiteRT, znany jako operator niestandardowy. Jeśli zamiast tego chcesz połączyć serię nieobsługiwanych (lub obsługiwanych) operatory TensorFlow w jednym uśrednionym zoptymalizowanym operatorze niestandardowym; zapoznaj się z operator fusing.
Korzystanie z operatorów niestandardowych składa się z 4 kroków.
Utwórz model TensorFlow. Upewnij się, że karta Zapisane Model (lub definicja wykresu) odnosi się do poprawnie nazwanego operatora LiteRT.
Przekonwertuj na model LiteRT. Upewnij się, że ustawiono właściwy atrybut konwertera LiteRT, aby przekonwertowano model.
Utwórz i zarejestruj operator. Ten by środowisko wykonawcze LiteRT wie, jak zmapować operatora do kodu wykonywalnego w C/C++.
Przetestuj i profiluj operatora. Jeśli jeśli chcesz przetestować tylko operator niestandardowy, najlepiej utworzyć model, na którym operatora niestandardowego i użyj funkcji benchmark_model programu.
Przyjrzyjmy się kompleksowemu przykładowi uruchamiania modelu z niestandardowym
tf.atan
(o nazwie Atan
, zapoznaj się z artykułem Tworzenie modelu TensorFlow), który
jest obsługiwany w TensorFlow, ale nie jest obsługiwany w LiteRT.
Przykładem operatora niestandardowego jest operator TensorFlow Text. Zobacz Samouczek konwertowania tekstu TF na LiteRT przedstawiający przykładowy kod.
Przykład: niestandardowy operator Atan
Przyjrzyjmy się przykładowi obsługi operatora TensorFlow, który
LiteRT nie ma. Załóżmy, że używamy operatora Atan
i że
tworzymy bardzo prosty model funkcji y = atan(x + offset)
, w której
Węzeł offset
można wytrenować.
Tworzenie modelu TensorFlow
Poniższy fragment kodu pozwala wytrenować prosty model TensorFlow. Ten model
zawiera operator niestandardowy o nazwie Atan
, który jest funkcją y = atan(x +
offset)
, gdzie offset
można trenować.
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
Jeśli w tym momencie spróbujesz wygenerować model LiteRT z domyślnym modelem flagi konwertera, pojawi się następujący komunikat o błędzie:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Konwertuj na model LiteRT
Utwórz model LiteRT z operatorami niestandardowymi, ustawiając konwerter
atrybut allow_custom_ops
jak poniżej:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Jeśli teraz uruchomisz ją za pomocą domyślnego tłumacza przy użyciu poleceń takich jak następujące:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Nadal pojawia się błąd:
Encountered unresolved custom op: Atan.
Utwórz i zarejestruj operator.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Niestandardowe operatory LiteRT są definiowane za pomocą prostego interfejsu API czystego C,
składa się z typu nieprzezroczystego (TfLiteRegistrationExternal
) i powiązanych funkcji.
TfLiteRegistrationExternal
jest typem nieprzezroczystym:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
przechowuje tożsamość operatora i implementację.
(Uwaga: ten operator różni się od jego operandów, które są przechowywane w
Węzły wykresu LiteRT dla węzłów, które wywołują operator).
Instancje tego typu są tworzone z wywołaniami
TfLiteRegistrationExternalCreate
i można ją zniszczyć, wywołując
TfLiteRegistrationExternalDelete
Tożsamość operatora jest ustawiana za pomocą parametrów w funkcji konstruktora
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.
);
Implementacja z użyciem operatorów może definiować „metody” z następującymi podpisami.
Wszystkie te metody są opcjonalne, ale dla operatora
operator musi określić i ustawić (za pomocą metody ustawiającej)
funkcji) co najmniej metod Prepare
i 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);
nazwy funkcji (lub prefiksy przestrzeni nazw w przypadku C++) w Twojej implementacji operacji; nie muszą odpowiadać nazwom funkcji z powyższego fragmentu kodu, ponieważ Interfejs Lite Custom Ops API będzie używać tylko ich adresów. Naprawdę zalecamy zadeklarować je w anonimowej przestrzeni nazw lub jako funkcje statyczne.
Warto jednak umieścić nazwę operatora jako przestrzeń nazw lub prefiks te nazwy funkcji:
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.
Ponieważ jest to interfejs API w języku C, te „metody” są zaimplementowane jako wskaźniki funkcji C w
typu TfLiteRegistrationExternal
, które są ustawiane przez przekazanie adresów
funkcji implementacji do odpowiednich funkcji ustawiających
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));
Więcej informacji:
common.h
aby dowiedzieć się więcej o TfLiteContext
i TfLiteNode
. TfLiteContext
zawiera błąd
oraz dostęp do obiektów globalnych, w tym wszystkich tensorów.
TfLiteNode
umożliwia implementacji operatorów dostęp do danych wejściowych i wyjściowych.
Gdy interpreter wczytuje model, wywołuje metodę Init()
raz dla każdej z nich.
na wykresie. Wartość Init()
zostanie wywołana więcej niż raz, jeśli op jest
używane wielokrotnie na wykresie. W przypadku operacji niestandardowych bufor konfiguracji będzie
z funkcją elastycznego bufora, która mapuje nazwy parametrów na ich wartości.
bufor jest pusty dla operacji wbudowanych, ponieważ interpreter przeanalizował już
parametry operacji. Implementacje jądra, które wymagają stanu, powinny je zainicjować
i przenieś własność na rozmówcę. W przypadku każdego wywołania Init()
nastąpi
odpowiednie wywołanie Free()
, dzięki czemu implementacje usuwają
bufor, który mogli przydzielić w Init()
.
Po każdej zmianie rozmiaru tensorów wejściowych tłumacz
wykres powiadamiający o zmianach. Dzięki temu mogą
zmienić rozmiar wewnętrznego bufora, sprawdzić poprawność kształtów i typów danych wejściowych oraz
i ponownie obliczyć kształty danych wyjściowych. Odbywa się to za pomocą metody Prepare()
,
implementacje mogą uzyskać dostęp do swojego stanu za pomocą
TfLiteOpaqueNodeGetUserData(node)
Na koniec przy każdym uruchomieniu wnioskowania tłumacz przegląda wykres,
metody Invoke()
i tutaj stan jest dostępny jako
TfLiteOpaqueNodeGetUserData(node)
Operacje niestandardowe można wdrażać poprzez zdefiniowanie tych „metod” funkcji, a następnie
definiują funkcję, która zwraca instancję TfLiteRegistrationExternal
utworzony przez wywołanie TfLiteRegistrationExternalCreate
, a następnie odpowiednie
metody ustawiania:
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; }
Pamiętaj, że rejestracja nie jest automatyczna i stanowi bezpośrednie wywołanie
Należy wykonać funkcję MyCustomOpRegistration
(szczegóły poniżej). Natomiast
standardowa BuiltinOpResolver
(dostępna z poziomu celu :builtin_ops
) przyjmuje
o rejestrację wbudowanych komponentów, niestandardowe operacje będą musiały być zbierane
oddzielne biblioteki niestandardowe.
Definiowanie jądra w środowisku wykonawczym LiteRT
Aby korzystać z op w LiteRT, wystarczy zdefiniować 2 funkcje,
(Prepare
i Eval
), a trzeci do utworzenia 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; }
Podczas inicjowania interfejsu OpResolver
dodaj niestandardowe działanie do resolvera (zobacz
poniżej. Spowoduje to zarejestrowanie operatora w LiteRT, więc
że LiteRT może użyć nowej implementacji. Pamiętaj, że ostatnie dwa
argumenty w funkcji TfLiteRegistration
odpowiadają AtanPrepare
i AtanEval
funkcji zdefiniowanych na potrzeby operacji niestandardowych. Jeśli korzystasz z usług AtanInit
i AtanFree
do inicjowania zmiennych używanych w działaniu i zwalniania miejsca,
to zostaną one dodane do pierwszych dwóch argumentów argumentu
TfLiteRegistration
; W tym przykładzie te argumenty są ustawione na nullptr
.
Rejestrowanie operatora w bibliotece jądra
Teraz musimy zarejestrować operatora w bibliotece jądra. Odbywa się to za pomocą
OpResolver
. Za kulisami tłumacz wczyta bibliotekę
jądra systemu, które zostaną przypisane do wykonywania poszczególnych operatorów w modelu.
Chociaż biblioteka domyślna zawiera tylko wbudowane jądra, możliwe jest
zastąpić/rozszerzyć ją niestandardowymi operatorami operacji biblioteki.
Klasa OpResolver
, która przekłada kody i nazwy operatorów na rzeczywiste
jest zdefiniowany w taki sposób:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Pamiętaj, że dla zapewnienia zgodności wstecznej ta klasa używa starszego typu konkretnego typu
TfLiteRegistration
zamiast nieprzezroczystego typu TfLiteRegistrationExternal
,
ale struktura TfLiteRegistration
zawiera pole registration_external
o wartości
wpisz TfLiteRegistrationExternal*
.
Klasy MutableOpResolver
i BuiltinOpResolver
pochodzą z
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.
};
Regularne używanie (bez niestandardowych operacji) wymaga używania interfejsu BuiltinOpResolver
i wpisz:
tflite::ops::builtin::BuiltinOpResolver resolver;
Aby dodać utworzoną powyżej operację niestandardową, możesz zamiast tego użyć MutableOpResolver
,
i wywołaj AddCustom
(zanim przekażesz program do rozpoznawania nazw
InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Jeśli zestaw działań wbudowanych zostanie uznany za za duży, nowy OpResolver
może zostać
generowany na podstawie określonego podzbioru operacji, prawdopodobnie tylko
w danym modelu. Jest to odpowiednik selektywnej rejestracji TensorFlow
(a jej prosta wersja jest dostępna w katalogu tools
).
Jeśli chcesz zdefiniować niestandardowe operatory w Javie, utwórz własną warstwę JNI i skompiluj własny AAR w tym kodzie Jni. Jeśli chcesz zdefiniować te operatory dostępne w Pythonie, możesz zarejestruj się w Kod otoki Pythona.
Możesz zastosować podobną procedurę, jak powyżej, aby obsługiwać zestaw
a nie za pomocą jednego operatora. Dodaj tyle operatorów AddCustom
według potrzeb. Dodatkowo MutableOpResolver
umożliwia również zastąpienie
implementacji wbudowanych za pomocą AddBuiltin
.
Testowanie i profilowanie operatora
Aby profilować swoją działalność za pomocą narzędzia Test porównawczy LiteRT, możesz użyć
narzędzie do analizy porównawczej
w przypadku LiteRT. Na potrzeby testów możesz utworzyć lokalną kompilację
LiteRT orientuje się w działaniach niestandardowych poprzez dodanie odpowiedniej wartości AddCustom
.
wywołaj (jak pokazano powyżej) do
register.cc
Sprawdzone metody
Ostrożnie optymalizuj przydziały pamięci i ich usuwanie. Przydzielam pamięć w trybie
Prepare
jest bardziej wydajny niż wInvoke
oraz przez przydzielanie pamięci przed pętlą jest lepsza od każdej iteracji. Użyj danych tensorów tymczasowych niż się malować (patrz punkt 2). Zamiast tego użyj wskaźników/odwołań nie kopiując ich.Jeśli struktura danych pozostanie niezmieniona podczas całej operacji, zalecamy przez wstępne przydzielanie pamięci przy użyciu tensorów tymczasowych. Może być konieczne użycie Struktura OpData odwołująca się do indeksów tensorów w innych funkcjach. Zobacz w jądro do splotu. Poniżej znajduje się przykładowy fragment kodu.
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; }
Jeśli nie kosztuje zbyt dużo pamięci, lepiej użyj statycznego, stałego rozmiaru tablica (lub wstępnie przydzielona tablica
std::vector
wResize
), zamiast używać dynamicznie przydzielał elementstd::vector
w każdej iteracji wykonania.Unikaj tworzenia instancji szablonów kontenerów biblioteki standardowej, które jeszcze nie są tego typu ponieważ wpływają na rozmiar plików binarnych. Na przykład, jeśli potrzebujesz
std::map
w operacji, której nie ma w innych jądrach, przy użyciu metodystd::vector
z mapowaniem indeksowania bezpośredniego może działać przy zachowaniu małym rozmiarze binarnym. Zobacz, jak inne jądra wykorzystują do uzyskiwania statystyk (lub o to pytaj).Sprawdź wskaźnik do wspomnienia zwróconego przez funkcję
malloc
. Jeśli ten wskaźnik tonullptr
, za pomocą tego wskaźnika nie należy wykonywać żadnych działań. Jeślimalloc
jest w funkcji i ma wyjście błędu, zwolnij pamięć, zanim .Aby znaleźć konkretny kod, użyj kodu
TF_LITE_OPAQUE_ENSURE(context, condition)
. Kod nie może pozostawać w pamięci, gdyTF_LITE_OPAQUE_ENSURE
, co oznacza, że tych makr należy użyć przed a jeśli zostaną przydzielone zasoby, które wycieknie,