Étant donné que la bibliothèque d'opérateurs intégrée LiteRT n'accepte qu'une un certain nombre d'opérateurs TensorFlow. Tous les modèles ne sont pas convertibles. Pour en savoir plus, consultez la section Compatibilité des opérateurs.
Pour autoriser la conversion, les utilisateurs peuvent fournir leur propre implémentation personnalisée d'un d'opérateur TensorFlow non pris en charge dans LiteRT, appelé opérateur personnalisé. Si vous préférez, vous pouvez combiner une série les opérateurs TensorFlow en un seul opérateur personnalisé optimisé. Pour en savoir plus, fusion des opérateurs.
L'utilisation des opérateurs personnalisés comprend quatre étapes.
Créer un modèle TensorFlow Assurez-vous que la case Model (ou Graph Def) fait référence à l'opérateur LiteRT correctement nommé.
Convertir en modèle LiteRT. Veillez à définir l'attribut de convertisseur LiteRT approprié pour : convertir le modèle.
Créez et enregistrez l'opérateur. Ce pour que l'environnement d'exécution LiteRT sache comment mapper votre opérateur de votre graphe en code C/C++ exécutable.
Testez et profilez votre opérateur. Si vous que vous souhaitez tester uniquement votre opérateur personnalisé, il est préférable de créer un modèle votre opérateur personnalisé benchmark_model programme.
Examinons un exemple de bout en bout illustrant l'exécution d'un modèle avec un
l'opérateur tf.atan
(nommé Atan
, reportez-vous à la section Créer un modèle TensorFlow).
est compatible avec TensorFlow, mais pas avec LiteRT.
L'opérateur TensorFlow Text est un exemple d'opérateur personnalisé. Consultez le Convert TF Text to LiteRT (Convertir TF Text en LiteRT) pour obtenir un exemple de code
Exemple: opérateur Atan
personnalisé
Prenons l'exemple d'un opérateur TensorFlow compatible
ce n'est pas le cas de LiteRT. Supposons que nous utilisons l'opérateur Atan
et que
nous construisons un modèle très simple pour une fonction y = atan(x + offset)
, où
offset
est apte à l'entraînement.
Créer un modèle TensorFlow
L'extrait de code suivant entraîne un modèle TensorFlow simple. Ce modèle vient
contient un opérateur personnalisé nommé Atan
, qui est une fonction y = atan(x +
offset)
, où offset
peut être entraîné.
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
À ce stade, si vous essayez de générer un modèle LiteRT avec vous obtenez le message d'erreur suivant:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Convertir en modèle LiteRT
Créer un modèle LiteRT avec des opérateurs personnalisés, en définissant le convertisseur
l'attribut allow_custom_ops
, comme indiqué ci-dessous:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
À ce stade, si vous l'exécutez avec l'interpréteur par défaut à l'aide de commandes telles que ce qui suit:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Vous obtiendrez toujours l'erreur suivante:
Encountered unresolved custom op: Atan.
Créez et enregistrez l'opérateur.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Les opérateurs personnalisés LiteRT sont définis à l'aide d'une simple API en C pur qui
est constitué d'un type opaque (TfLiteRegistrationExternal
) et de fonctions associées.
TfLiteRegistrationExternal
est un type opaque:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
stocke l'identité et l'implémentation de l'opérateur.
(Notez que l'opérateur est différent de ses opérandes, qui sont stockés dans la
Nœuds de graphe LiteRT pour les nœuds qui appellent l'opérateur)
Les instances de ce type sont construites avec des appels vers
TfLiteRegistrationExternalCreate
et peut être détruite en appelant
TfLiteRegistrationExternalDelete
L'identité de l'opérateur est définie via les paramètres de la fonction constructeur.
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.
);
L'implémentation de l'opérateur peut définir des "méthodes" avec les signatures suivantes.
Toutes ces méthodes sont facultatives, mais pour qu'un opérateur puisse
évalué, l'implémentation de l'opérateur doit définir et régler (à l'aide du setter
fonctions) au moins les méthodes Prepare
et 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);
Les noms de fonctions (ou préfixes d'espace de noms, pour C++) dans la mise en œuvre de votre opération ne doivent pas nécessairement correspondre aux noms de fonction dans l'extrait de code ci-dessus, car le fichier L'API Lite custom ops n'utilisera que leurs adresses. En effet, nous vous recommandons Déclarez-les dans un espace de noms anonyme ou en tant que fonctions statiques.
Mais c'est une bonne idée d'inclure votre nom d'opérateur en tant qu’espace de noms ou préfixe sur ces noms de fonction:
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.
Puisqu'il s'agit d'une API C, ces "méthodes" sont implémentés en tant que pointeurs de fonction C dans
du type TfLiteRegistrationExternal
, qui sont définis en transmettant les adresses
vos fonctions d'implémentation
aux fonctions setter correspondantes
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));
Consultez
common.h
pour en savoir plus sur TfLiteContext
et TfLiteNode
. TfLiteContext
renvoie une erreur
de création de rapports et l'accès aux objets globaux, y compris à tous les Tensors.
TfLiteNode
permet aux implémentations d'opérateur d'accéder à leurs entrées et sorties.
Lorsque l'interpréteur charge un modèle, il appelle la méthode Init()
une fois pour chaque
dans le graphique. Une valeur Init()
donnée sera appelée plusieurs fois si l'opération est
utilisées plusieurs fois dans le graphique. Pour les opérations personnalisées,
un tampon de configuration
contenant un tampon flexible qui mappe les noms de paramètres à leurs valeurs. La
est vide pour les opérations intégrées, car l'interpréteur a déjà analysé
op. Les implémentations du noyau qui nécessitent un état doivent l'initialiser
et transférer la propriété à l'appelant. Pour chaque appel Init()
, vous aurez
un appel correspondant à Free()
, ce qui permet aux implémentations de supprimer
qu'ils ont peut-être alloué dans Init()
.
Chaque fois que les Tensors d'entrée sont redimensionnés, l'interpréteur passe par les
graphique signalant les implémentations de la modification. Cela leur donne
la possibilité de
redimensionner leur tampon interne, vérifier la validité des formes et types de saisie ;
pour recalculer les formes de sortie. Tout cela s'effectue via la méthode Prepare()
.
les implémentations peuvent accéder à leur état
TfLiteOpaqueNodeGetUserData(node)
Enfin, à chaque exécution d'une inférence, l'interpréteur parcourt le graphique, en appelant
la méthode Invoke()
. Ici aussi, l'état est disponible en tant que
TfLiteOpaqueNodeGetUserData(node)
Vous pouvez implémenter des opérations personnalisées en définissant ces "méthodes" fonctions, puis
définir une fonction qui renvoie une instance de TfLiteRegistrationExternal
;
créée en appelant TfLiteRegistrationExternalCreate
, puis les valeurs
méthodes setter:
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; }
Notez que l'inscription n'est pas automatique et ne constitue pas un appel explicite à votre
La fonction MyCustomOpRegistration
doit être créée (voir les détails ci-dessous). Alors que le
standard BuiltinOpResolver
(disponible depuis la cible :builtin_ops
) prend
de l'enregistrement des builds intégrés, les opérations personnalisées devront être collectées
bibliothèques personnalisées distinctes.
Définir le noyau dans l'environnement d'exécution LiteRT
Pour utiliser l'opération dans LiteRT, il suffit de définir deux fonctions
(Prepare
et Eval
) et un troisième pour construire un 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; }
Lors de l'initialisation de OpResolver
, ajoutez l'opération personnalisée dans le résolveur (voir
ci-dessous). L'opérateur sera alors enregistré dans LiteRT.
que LiteRT puisse utiliser la nouvelle implémentation. Notez que les deux derniers
arguments dans TfLiteRegistration
correspondent à AtanPrepare
et AtanEval
que vous avez définies pour l'opération personnalisée. Si vous avez utilisé AtanInit
et AtanFree
pour initialiser les variables utilisées dans l'opération
et pour libérer de l'espace,
respectivement, elles seront ajoutées aux deux premiers arguments de
TfLiteRegistration
; ces arguments sont définis sur nullptr
dans cet exemple.
Enregistrer l'opérateur auprès de la bibliothèque du noyau
Nous devons maintenant enregistrer l'opérateur auprès de la bibliothèque du noyau. Cela se fait avec
un OpResolver
. En arrière-plan, l'interprète
charge une bibliothèque de
noyaux qui seront affectés pour exécuter
chacun des opérateurs du modèle.
Bien que la bibliothèque par défaut ne contienne que des noyaux intégrés, il est possible
remplacez/enrichissez-le par des opérateurs
d'opérations de bibliothèque personnalisés.
La classe OpResolver
, qui traduit les codes et les noms des opérateurs en
est définie comme ceci:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Notez que pour assurer la rétrocompatibilité, cette classe utilise l'ancien type concret
TfLiteRegistration
au lieu du type opaque TfLiteRegistrationExternal
,
mais la structure TfLiteRegistration
contient un champ registration_external
saisissez TfLiteRegistrationExternal*
.
Les classes MutableOpResolver
et BuiltinOpResolver
sont dérivées de
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.
};
Une utilisation standard (sans opérations personnalisées) nécessite l'utilisation de BuiltinOpResolver
.
et écrire:
tflite::ops::builtin::BuiltinOpResolver resolver;
Pour ajouter l'opération personnalisée créée ci-dessus, vous pouvez utiliser à la place un MutableOpResolver
,
et appelez AddCustom
(avant de transmettre le résolveur au
InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Si l'ensemble d'opérations intégrées est considéré comme trop volumineux, une nouvelle OpResolver
peut être
du code généré à partir d'un sous-ensemble donné d'opérations, peut-être uniquement ceux contenus
dans un modèle donné. Il s'agit de l'équivalent de l'enregistrement sélectif de TensorFlow.
(et une version simple est disponible dans le répertoire tools
).
Si vous souhaitez définir vos opérateurs personnalisés en Java, vous devez actuellement créer votre propre couche JNI personnalisée et compiler votre propre AAR dans ce code jni. De même, si vous souhaitez définir ces opérateurs disponibles en Python, vous pouvez placez vos inscriptions Code wrapper Python.
Notez qu'un processus similaire à celui présenté ci-dessus peut être suivi pour prendre en charge un ensemble de
des opérations au lieu d'un seul opérateur. Ajoutez simplement autant d'opérateurs AddCustom
selon vos besoins. De plus, MutableOpResolver
vous permet aussi d'ignorer
les implémentations de "builtins" à l'aide de AddBuiltin
.
Tester et profiler votre opérateur
Pour profiler votre opération avec l'outil d'analyse comparative LiteRT, vous pouvez utiliser
outil de modélisation de modèles de benchmark
pour LiteRT. À des fins de test, vous pouvez créer votre build local
Vous n'avez pas besoin de vous informer facilement de votre opération personnalisée en ajoutant le AddCustom
approprié.
(comme indiqué ci-dessus) au
register.cc
Bonnes pratiques
Optimisez les allocations et désallocations de mémoire avec prudence. Allouer de la mémoire est plus efficace dans
Prepare
que dansInvoke
, et l'allocation de mémoire avant une boucle est préférable à chaque itération. Utiliser des données de Tensors temporaires plutôt que de vous espionner vous-même (voir le point 2). Utilisez plutôt des pointeurs/références de copier autant que possible.Si une structure de données persiste pendant toute la durée de l'opération, nous vous conseillons pré-allouez la mémoire à l'aide de Tensors temporaires. Vous devrez peut-être utiliser un Structure OpData pour référencer les index de Tensor dans d'autres fonctions. Consultez le exemple dans noyau pour la convolution. Vous trouverez un exemple d'extrait de code ci-dessous.
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; }
S'il ne coûte pas trop de mémoire gaspillée, utilisez une taille fixe et statique. (ou un
std::vector
préalloué dansResize
) plutôt que d'utiliser unstd::vector
alloué de manière dynamique à chaque itération d'exécution.Évitez d'instancier des modèles de conteneur de bibliothèque standard qui ne car elles affectent la taille binaire. Par exemple, si vous avez besoin d'un
std::map
dans votre opération qui n'existe pas dans d'autres noyaux, à l'aide d'unstd::vector
avec mappage d'indexation directe pourrait fonctionner tout en gardant le la taille binaire la plus petite. Découvrez ce que les autres noyaux utilisent pour obtenir des informations (ou demander).Vérifiez le pointeur vers la mémoire renvoyée par
malloc
. Si ce pointeur estnullptr
, aucune opération ne doit être effectuée à l'aide de ce pointeur. Si vousmalloc
dans une fonction et si une sortie d'erreur s'affiche, libérez de la mémoire avant de .Utilisez
TF_LITE_OPAQUE_ENSURE(context, condition)
pour rechercher une . Votre code ne doit pas laisser de mémoire en attente lorsqueTF_LITE_OPAQUE_ENSURE
est utilisé, c'est-à-dire que ces macros doivent être utilisées avant toutes les ressources qui risquent d'être divulguées sont allouées.