Meqenëse biblioteka e operatorëve të integruar LiteRT mbështet vetëm një numër të kufizuar operatorësh TensorFlow, jo çdo model është i konvertueshëm. Për detaje, referojuni përputhshmërisë së operatorit .
Për të lejuar konvertimin, përdoruesit mund të ofrojnë implementimin e tyre personal të një operatori TensorFlow të pambështetur në LiteRT, i njohur si operator personal. Nëse në vend të kësaj, dëshironi të kombinoni një seri operatorësh të pambështetur (ose të mbështetur) TensorFlow në një operator personal të optimizuar të vetëm të bashkuar, referojuni fuzionit të operatorit .
Përdorimi i operatorëve me porosi përbëhet nga katër hapa.
Krijo një model TensorFlow. Sigurohuni që Saved Model (ose Graph Def) i referohet operatorit LiteRT të emërtuar saktë.
Konvertoni në një model LiteRT. Sigurohuni që keni vendosur atributin e duhur të konvertuesit LiteRT në mënyrë që të konvertohet me sukses modeli.
Krijoni dhe regjistroni operatorin. Kjo është në mënyrë që koha e ekzekutimit të LiteRT të dijë se si të hartojë operatorin tuaj dhe parametrat në grafikun tuaj në kodin C/C++ të ekzekutueshëm.
Testoni dhe profilizoni operatorin tuaj. Nëse dëshironi të testoni vetëm operatorin tuaj të personalizuar, është më mirë të krijoni një model vetëm me operatorin tuaj të personalizuar dhe të përdorni programin benchmark_model .
Le të shqyrtojmë një shembull nga skaji në skaj të ekzekutimit të një modeli me një operator të personalizuar tf.atan
(i quajtur si Atan
, referojuni Krijo një model TensorFlow. ) i cili mbështetet në TensorFlow, por nuk mbështetet në LiteRT.
Operatori TensorFlow Text është një shembull i një operatori të personalizuar. Shihni tutorialin "Konvertoni TF Text në LiteRT" për një shembull kodi.
Shembull: Operatori i personalizuar Atan
Le të shohim një shembull të mbështetjes së një operatori TensorFlow që LiteRT nuk e ka. Supozojmë se po përdorim operatorin Atan
dhe se po ndërtojmë një model shumë të thjeshtë për një funksion y = atan(x + offset)
, ku offset
është i trajnueshëm.
Krijo një model TensorFlow
Pjesa e mëposhtme e kodit trajnon një model të thjeshtë TensorFlow. Ky model përmban vetëm një operator personal të quajtur Atan
, i cili është një funksion y = atan(x + offset)
, ku offset
është i trajnueshëm.
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
Në këtë pikë, nëse përpiqeni të gjeneroni një model LiteRT me flamujt e konvertuesit të parazgjedhur, do të merrni mesazhin e mëposhtëm të gabimit:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Konvertoni në një model LiteRT
Krijoni një model LiteRT me operatorë të personalizuar, duke vendosur atributin e konvertuesit allow_custom_ops
siç tregohet më poshtë:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Në këtë pikë, nëse e ekzekutoni me përkthyesin e paracaktuar duke përdorur komanda të tilla si më poshtë:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Ju ende do të merrni gabimin:
Encountered unresolved custom op: Atan.
Krijoni dhe regjistroni operatorin.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Operatorët e personalizuar të LiteRT përcaktohen duke përdorur një API të thjeshtë C të pastër që përbëhet nga një lloj i errët ( TfLiteRegistrationExternal
) dhe funksione të ngjashme.
TfLiteRegistrationExternal
është një lloj i errët:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
ruan identitetin dhe zbatimin e operatorit. (Vini re se operatori është i ndryshëm nga operandët e tij, të cilët ruhen në nyjet grafike LiteRT për nyjet që thërrasin operatorin.)
Instancat e këtij lloji janë ndërtuar me thirrje në TfLiteRegistrationExternalCreate
dhe mund të shkatërrohen duke thirrur TfLiteRegistrationExternalDelete
.
Identiteti i operatorit vendoset nëpërmjet parametrave të funksionit konstruktor 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.
);
Implementimi i operatorit mund të përcaktojë "metodat" me nënshkrimet e mëposhtme. Të gjitha këto metoda janë opsionale, por që një operator të vlerësohet me sukses, zbatimi i operatorit duhet të përcaktojë dhe vendosë (duke përdorur funksionet e vendosësit) të paktën metodat Prepare
dhe 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);
Emrat e funksioneve (ose prefikset e hapësirës së emrave, për C++) në zbatimin tuaj të funksionit nuk duhet të përputhen me emrat e funksioneve në fragmentin e kodit të mësipërm, pasi API-ja e funksioneve me porosi TF Lite do të përdorë vetëm adresat e tyre. Në të vërtetë ju rekomandojmë që t'i deklaroni ato në një hapësirë anonime ose si funksione statike.
Por është një ide e mirë të përfshini emrin e operatorit tuaj si një hapësirë emri ose prefiks në këto emra funksionesh:
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.
Meqenëse ky është një API C, këto "metoda" zbatohen si tregues të funksionit C në llojin TfLiteRegistrationExternal
, të cilët vendosen duke kaluar adresat e funksioneve tuaja të zbatimit te funksionet përkatëse të vendosësit 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));
Referojuni common.h
për detaje mbi TfLiteContext
dhe TfLiteNode
. TfLiteContext
ofron lehtësira për raportimin e gabimeve dhe akses në objektet globale, duke përfshirë të gjithë tensorët. TfLiteNode
lejon implementimet e operatorëve të kenë akses në hyrjet dhe daljet e tyre.
Kur interpretuesi ngarkon një model, ai thërret metodën Init()
një herë për secilën nyje në grafik. Një Init()
e dhënë do të thirret më shumë se një herë nëse op përdoret shumë herë në grafik. Për funksionet e personalizuara do të sigurohet një buffer konfigurimi, që përmban një flexbuffer që lidh emrat e parametrave me vlerat e tyre. Buferi është bosh për funksionet e integruara sepse interpretuesi i ka analizuar tashmë parametrat op. Zbatimet e kernelit që kërkojnë gjendje duhet ta inicializojnë këtu dhe t'ia transferojnë pronësinë telefonuesit. Për çdo thirrje Init()
, do të ketë një thirrje përkatëse për Free()
, duke i lejuar implementimet të heqin bufferin që mund të kenë ndarë në Init()
.
Sa herë që tensorët e hyrjes ndryshohen, përkthyesi do të kalojë përmes grafikut duke njoftuar zbatimin e ndryshimit. Kjo u jep atyre mundësinë për të ndryshuar madhësinë e tamponit të tyre të brendshëm, të kontrollojnë vlefshmërinë e formave dhe llojeve të hyrjes dhe të rillogaritin format e daljes. E gjithë kjo bëhet përmes metodës Prepare()
dhe implementimet mund të hyjnë në gjendjen e tyre duke përdorur TfLiteOpaqueNodeGetUserData(node)
.
Së fundi, sa herë që ekzekutohet përfundimi, përkthyesi përshkon grafikun duke thirrur metodën Invoke()
dhe edhe këtu gjendja është e disponueshme si TfLiteOpaqueNodeGetUserData(node)
.
Opsionet e personalizuara mund të zbatohen duke përcaktuar ato funksione "metodë" dhe më pas duke përcaktuar një funksion që kthen një shembull të TfLiteRegistrationExternal
të ndërtuar duke thirrur TfLiteRegistrationExternalCreate
dhe më pas metodat përkatëse të vendosësit:
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; }
Vini re se regjistrimi nuk është automatik dhe duhet të bëhet një thirrje e qartë për funksionin tuaj MyCustomOpRegistration
(shih detajet më poshtë). Ndërsa standardi BuiltinOpResolver
(i disponueshëm nga objektivi :builtin_ops
) kujdeset për regjistrimin e indeve të integruara, operacionet e personalizuara do të duhet të mblidhen në biblioteka të veçanta me porosi.
Përcaktimi i kernelit në kohën e ekzekutimit të LiteRT
Gjithçka që duhet të bëjmë për të përdorur op-in në LiteRT është të përcaktojmë dy funksione ( Prepare
dhe Eval
), dhe një të tretën për të ndërtuar një 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; }
Kur inicializon OpResolver
, shtoni op-in e personalizuar në zgjidhës (shih më poshtë për një shembull). Kjo do të regjistrojë operatorin me LiteRT në mënyrë që LiteRT të mund të përdorë zbatimin e ri. Vini re se dy argumentet e fundit në TfLiteRegistration
korrespondojnë me funksionet AtanPrepare
dhe AtanEval
që keni përcaktuar për opcionin e personalizuar. Nëse keni përdorur funksionet AtanInit
dhe AtanFree
për të inicializuar variablat e përdorur në op dhe për të liruar hapësirë, përkatësisht, atëherë ato do të shtohen në dy argumentet e para të TfLiteRegistration
; ato argumente janë vendosur në nullptr
në këtë shembull.
Regjistroni operatorin me bibliotekën e kernelit
Tani duhet të regjistrojmë operatorin me bibliotekën e kernelit. Kjo bëhet me një OpResolver
. Prapa skenës, përkthyesi do të ngarkojë një bibliotekë me kernel të cilët do të caktohen për të ekzekutuar secilin prej operatorëve në model. Ndërsa biblioteka e paracaktuar përmban vetëm kernelet e integruar, është e mundur që të zëvendësohet/shtohet me një operator të personalizuar të bibliotekës.
Klasa OpResolver
, e cila përkthen kodet dhe emrat e operatorëve në kodin aktual, përcaktohet si kjo:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Vini re se për pajtueshmërinë e pasme, kjo klasë përdor tipin më të vjetër të betonit TfLiteRegistration
në vend të tipit opake TfLiteRegistrationExternal
, por struktura TfLiteRegistration
përmban një fushë registration_external
të tipit TfLiteRegistrationExternal*
.
Klasat MutableOpResolver
dhe BuiltinOpResolver
rrjedhin nga 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.
};
Përdorimi i rregullt (pa funksione të personalizuara) kërkon që të përdorni BuiltinOpResolver
dhe të shkruani:
tflite::ops::builtin::BuiltinOpResolver resolver;
Për të shtuar opsionin e personalizuar të krijuar më sipër, në vend të kësaj mund të përdorni një MutableOpResolver
dhe të telefononi AddCustom
(para se të kaloni zgjidhësin te InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Nëse grupi i funksioneve të integruara konsiderohet të jetë shumë i madh, një OpResolver
i ri mund të gjenerohet me kod bazuar në një nëngrup të caktuar të opcioneve, ndoshta vetëm ato që përmbahen në një model të caktuar. Ky është ekuivalenti i regjistrimit selektiv të TensorFlow (dhe një version i thjeshtë i tij është i disponueshëm në direktorinë tools
).
Nëse dëshironi të përcaktoni operatorët tuaj të personalizuar në Java, aktualisht do t'ju duhet të ndërtoni shtresën tuaj të personalizuar JNI dhe të përpiloni AAR-in tuaj në këtë kod jni . Në mënyrë të ngjashme, nëse dëshironi të përcaktoni këta operatorë të disponueshëm në Python, mund t'i vendosni regjistrimet tuaja në kodin e mbështjellësit të Python .
Vini re se një proces i ngjashëm si më sipër mund të ndiqet për mbështetjen e një grupi operacionesh në vend të një operatori të vetëm. Thjesht shtoni sa më shumë operatorë AddCustom
që ju nevojiten. Përveç kësaj, MutableOpResolver
ju lejon gjithashtu të anashkaloni implementimet e integruara duke përdorur AddBuiltin
.
Testoni dhe profilizoni operatorin tuaj
Për të profilizuar funksionin tuaj me mjetin e standardeve LiteRT, mund të përdorni mjetin e modelit të standardeve për LiteRT. Për qëllime testimi, mund ta ndërgjegjësoni ndërtimin tuaj lokal të LiteRT për funksionin tuaj personal duke shtuar thirrjen e duhur AddCustom
(siç tregohet më lart) në register.cc
Praktikat më të mira
Optimizoni me kujdes ndarjet e memories dhe ç'alokimet. Shpërndarja e kujtesës në
Prepare
është më efikase sesa nëInvoke
, dhe shpërndarja e memories para një cikli është më e mirë se në çdo përsëritje. Përdorni të dhënat e përkohshme të tensorëve në vend që të përdorni veten (shih pikën 2). Përdorni tregues/referenca në vend që të kopjoni sa më shumë që të jetë e mundur.Nëse një strukturë e të dhënave do të vazhdojë gjatë gjithë operacionit, ne këshillojmë paracaktimin e memories duke përdorur tensorë të përkohshëm. Ju mund të keni nevojë të përdorni një strukturë OpData për të referuar indekset tensor në funksione të tjera. Shihni shembullin në kernel për konvolucionin . Një fragment kodi shembull është më poshtë.
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; }
Nëse nuk kushton shumë memorie të humbur, preferoni të përdorni një grup statik me madhësi fikse (ose një
std::vector
të paracaktuar nëResize
) në vend që të përdorni njëstd::vector
të alokuar dinamikisht çdo përsëritje të ekzekutimit.Shmangni instantimin e modeleve standarde të kontejnerëve të bibliotekës që nuk ekzistojnë tashmë, sepse ato ndikojnë në madhësinë binare. Për shembull, nëse keni nevojë për një
std::map
në operacionin tuaj që nuk ekziston në kernelet e tjera, përdorimi i njëstd::vector
me hartëzimin direkt të indeksimit mund të funksionojë duke mbajtur madhësinë binare të vogël. Shihni se çfarë përdorin bërthamat e tjera për të fituar njohuri (ose pyetur).Kontrolloni treguesin në kujtesën e kthyer nga
malloc
. Nëse ky tregues ështënullptr
, asnjë operacion nuk duhet të kryhet duke përdorur atë tregues. Nëse kenimalloc
në një funksion dhe keni një dalje gabimi, shpërndani kujtesën përpara se të dilni.Përdor
TF_LITE_OPAQUE_ENSURE(context, condition)
për të kontrolluar për një gjendje specifike. Kodi juaj nuk duhet të lërë të varur memorien kur përdoretTF_LITE_OPAQUE_ENSURE
, dmth, këto makro duhet të përdoren përpara se të ndahen ndonjë burim që do të rrjedhë.